Comparing version 1.0.11 to 1.1.0
@@ -33,2 +33,3 @@ | ||
hash: dloc.hash, | ||
history: false, | ||
@@ -45,3 +46,3 @@ check: function () { | ||
if (this.mode === 'modern') { | ||
window.onhashchange(); | ||
this.history === true ? window.onpopstate() : window.onhashchange(); | ||
} | ||
@@ -53,4 +54,5 @@ else { | ||
init: function (fn) { | ||
init: function (fn, history) { | ||
var self = this; | ||
this.history = history; | ||
@@ -61,5 +63,5 @@ if (!window.Router.listeners) { | ||
function onchange() { | ||
function onchange(onChangeEvent) { | ||
for (var i = 0, l = window.Router.listeners.length; i < l; i++) { | ||
window.Router.listeners[i](); | ||
window.Router.listeners[i](onChangeEvent); | ||
} | ||
@@ -71,3 +73,16 @@ } | ||
|| document.documentMode > 7)) { | ||
window.onhashchange = onchange; | ||
// At least for now HTML5 history is available for 'modern' browsers only | ||
if (this.history === true) { | ||
// There is an old bug in Chrome that causes onpopstate to fire even | ||
// upon initial page load. Since the handler is run manually in init(), | ||
// this would cause Chrome to run it twise. Currently the only | ||
// workaround seems to be to set the handler after the initial page load | ||
// http://code.google.com/p/chromium/issues/detail?id=63040 | ||
setTimeout(function() { | ||
window.onpopstate = onchange; | ||
}, 500); | ||
} | ||
else { | ||
window.onhashchange = onchange; | ||
} | ||
this.mode = 'modern'; | ||
@@ -124,3 +139,10 @@ } | ||
dloc.hash = (s[0] === '/') ? s : '/' + s; | ||
if (this.history === true) { | ||
window.history.pushState({}, document.title, s); | ||
// Fire an onpopstate event manually since pushing does not obviously | ||
// trigger the pop event. | ||
this.fire(); | ||
} else { | ||
dloc.hash = (s[0] === '/') ? s : '/' + s; | ||
} | ||
return this; | ||
@@ -161,2 +183,4 @@ }, | ||
this.historySupport = (window.history != null ? window.history.pushState : null) != null | ||
this.configure(); | ||
@@ -168,16 +192,29 @@ this.mount(routes || {}); | ||
var self = this; | ||
this.handler = function() { | ||
var hash = dloc.hash.replace(/^#/, ''); | ||
self.dispatch('on', hash); | ||
this.handler = function(onChangeEvent) { | ||
var url = self.history === true ? self.getPath() : onChangeEvent.newURL.replace(/.*#/, ''); | ||
self.dispatch('on', url); | ||
}; | ||
if (dloc.hash === '' && r) { | ||
dloc.hash = r; | ||
listener.init(this.handler, this.history); | ||
if (this.history === false) { | ||
if (dloc.hash === '' && r) { | ||
dloc.hash = r; | ||
} else if (dloc.hash.length > 0) { | ||
self.dispatch('on', dloc.hash.replace(/^#/, '')); | ||
} | ||
} | ||
else { | ||
routeTo = dloc.hash === '' && r ? r : dloc.hash.length > 0 ? dloc.hash.replace(/^#/, '') : null; | ||
if (routeTo) { | ||
window.history.replaceState({}, document.title, routeTo); | ||
} | ||
if (dloc.hash.length > 0) { | ||
this.handler(); | ||
// Router has been initialized, but due to the chrome bug it will not | ||
// yet actually route HTML5 history state changes. Thus, decide if should route. | ||
if (routeTo || this.run_in_init === true) { | ||
this.handler(); | ||
} | ||
} | ||
listener.init(this.handler); | ||
return this; | ||
@@ -187,3 +224,3 @@ }; | ||
Router.prototype.explode = function () { | ||
var v = dloc.hash; | ||
var v = this.history === true ? this.getPath() : dloc.hash; | ||
if (v[1] === '/') { v=v.slice(1) } | ||
@@ -254,1 +291,9 @@ return v.slice(1, v.length).split("/"); | ||
}; | ||
Router.prototype.getPath = function () { | ||
var path = window.location.pathname; | ||
if (path.substr(0, 1) !== '/') { | ||
path = '/' + path; | ||
} | ||
return path; | ||
}; |
@@ -147,2 +147,6 @@ /* | ||
// Client only, but browser.js does not include a super implementation | ||
this.history = (options.html5history && this.historySupport) || false; | ||
this.run_in_init = (this.history === true && options.run_handler_in_init !== false); | ||
// | ||
@@ -149,0 +153,0 @@ // TODO: Global once |
@@ -5,3 +5,3 @@ { | ||
"author": "Nodejitsu Inc. <info@nodejitsu.com>", | ||
"version": "1.0.11", | ||
"version": "1.1.0", | ||
"maintainers": [ | ||
@@ -8,0 +8,0 @@ "hij1nx <paolo@nodejitsu.com>", |
100
README.md
@@ -29,3 +29,3 @@ <img src="https://github.com/flatiron/director/raw/master/img/director.png" /> | ||
Client-side routing (aka hash-routing) allows you to specify some information about the state of the application using the URL. So that when the user visits a specific URL, the application can be transformed accordingly. | ||
Client-side routing (aka hash-routing) allows you to specify some information about the state of the application using the URL. So that when the user visits a specific URL, the application can be transformed accordingly. | ||
@@ -137,3 +137,3 @@ <img src="https://github.com/flatiron/director/raw/master/img/hashRoute.png" /> | ||
Director handles routing for HTTP requests similar to `journey` or `express`: | ||
Director handles routing for HTTP requests similar to `journey` or `express`: | ||
@@ -166,3 +166,3 @@ ```js | ||
// setup a server and when there is a request, dispatch the | ||
// route that was requestd in the request object. | ||
// route that was requested in the request object. | ||
// | ||
@@ -198,9 +198,9 @@ var server = http.createServer(function (req, res) { | ||
var director = require('director'); | ||
var router = new director.cli.Router(); | ||
router.on('create', function () { | ||
console.log('create something'); | ||
}); | ||
router.on(/destroy/, function () { | ||
@@ -237,2 +237,3 @@ console.log('destroy something'); | ||
* [Resources](#resources) | ||
* [History API](#history-api) | ||
* [Instance Methods](#instance-methods) | ||
@@ -258,3 +259,3 @@ * [Attach Properties to `this`](#attach-to-this) | ||
// | ||
var routes = { | ||
var routes = { | ||
// | ||
@@ -273,3 +274,3 @@ // a route which assigns the function `bark`. | ||
// | ||
var router = Router(routes); | ||
var router = Router(routes); | ||
``` | ||
@@ -286,3 +287,3 @@ | ||
var router = new Router().init(); | ||
router.on('/some/resource', function () { | ||
@@ -299,6 +300,6 @@ // | ||
var router = new director.http.Router(); | ||
router.get(/\/some\/resource/, function () { | ||
// | ||
// Do something on an GET to `/some/resource` | ||
// Do something on an GET to `/some/resource` | ||
// | ||
@@ -315,3 +316,3 @@ }); | ||
var router = new director.http.Router(); | ||
// | ||
@@ -325,3 +326,3 @@ // Create routes inside the `/users` scope. | ||
// | ||
this.post(function (id) { | ||
@@ -332,3 +333,3 @@ // | ||
}); | ||
this.get(function (id) { | ||
@@ -339,3 +340,3 @@ // | ||
}); | ||
this.get(/\/friends/, function (id) { | ||
@@ -373,3 +374,3 @@ // | ||
* **recurse:** Controls [route recursion](#route-recursion). Use `forward`, `backward`, or `false`. Default is `false` Client-side, and `backward` Server-side. | ||
* **recurse:** Controls [route recursion](#route-recursion). Use `forward`, `backward`, or `false`. Default is `false` Client-side, and `backward` Server-side. | ||
* **strict:** If set to `false`, then trailing slashes (or other delimiters) are allowed in routes. Default is `true`. | ||
@@ -386,2 +387,4 @@ * **async:** Controls [async routing](#async-routing). Use `true` or `false`. Default is `false`. | ||
* **after:** A function (or list of functions) to call when a given route is no longer the active route. | ||
* **html5history:** If set to `true` and client supports `pushState()`, then uses HTML5 History API instead of hash fragments. See [History API](#history-api) for more information. | ||
* **run_handler_in_init:** If `html5history` is enabled, the route handler by default is executed upon `Router.init()` since with real URIs the router can not know if it should call a route handler or not. Setting this to `false` disables the route handler initial execution. | ||
@@ -410,3 +413,3 @@ <a name="url-matching"></a> | ||
``` js | ||
var router = Router({ | ||
var router = Router({ | ||
// | ||
@@ -442,11 +445,11 @@ // given the route '/hello/world'. | ||
When you are using the same route fragments it is more descriptive to define these fragments by name and then use them in your [Routing Table](#routing-table) or [Adhoc Routes](#adhoc-routing). Consider a simple example where a `userId` is used repeatedly. | ||
When you are using the same route fragments it is more descriptive to define these fragments by name and then use them in your [Routing Table](#routing-table) or [Adhoc Routes](#adhoc-routing). Consider a simple example where a `userId` is used repeatedly. | ||
``` js | ||
// | ||
// Create a router. This could also be director.cli.Router() or | ||
// Create a router. This could also be director.cli.Router() or | ||
// director.http.Router(). | ||
// | ||
var router = new director.Router(); | ||
// | ||
@@ -456,3 +459,3 @@ // A route could be defined using the `userId` explicitly. | ||
router.on(/([\w-_]+)/, function (userId) { }); | ||
// | ||
@@ -462,3 +465,3 @@ // Define a shorthand for this fragment called `userId`. | ||
router.param('userId', /([\\w\\-]+)/); | ||
// | ||
@@ -504,3 +507,3 @@ // Now multiple routes can be defined with the same | ||
// | ||
on: growl | ||
on: growl | ||
}, | ||
@@ -547,3 +550,3 @@ // | ||
// | ||
on: function() { return false; } | ||
on: function() { return false; } | ||
}, | ||
@@ -553,6 +556,6 @@ // | ||
// | ||
on: bark | ||
on: bark | ||
} | ||
}; | ||
// | ||
@@ -567,3 +570,3 @@ // This feature works in reverse with recursion set to true. | ||
Before diving into how Director exposes async routing, you should understand [Route Recursion](#route-recursion). At it's core route recursion is about evaluating a series of functions gathered when traversing the [Routing Table](#routing-table). | ||
Before diving into how Director exposes async routing, you should understand [Route Recursion](#route-recursion). At it's core route recursion is about evaluating a series of functions gathered when traversing the [Routing Table](#routing-table). | ||
@@ -581,3 +584,3 @@ Normally this series of functions is evaluated synchronously. In async routing, these functions are evaluated asynchronously. Async routing can be extremely useful both on the client-side and the server-side: | ||
var router = new director.Router(); | ||
router.on('/:foo/:bar/:bazz', function (foo, bar, bazz) { | ||
@@ -594,3 +597,3 @@ // | ||
var router = new director.http.Router().configure({ async: true }); | ||
router.on('/:foo/:bar/:bazz', function (foo, bar, bazz, next) { | ||
@@ -627,2 +630,11 @@ // | ||
<a name="history-api"></a> | ||
## History API | ||
**Available on the Client-side only.** Director supports using HTML5 History API instead of hash fragments for navigation. To use the API, pass `{html5history: true}` to `configure()`. Use of the API is enabled only if the client supports `pushState()`. | ||
Using the API gives you cleaner URIs but they come with a cost. Unlike with hash fragments your route URIs must exist. When the client enters a page, say http://foo.com/bar/baz, the web server must respond with something meaningful. Usually this means that your web server checks the URI points to something that, in a sense, exists, and then serves the client the JavaScript application. | ||
If you're after a single-page application you can not use plain old `<a href="/bar/baz">` tags for navigation anymore. When such link is clicked, web browsers try to ask for the resource from server which is not of course desired for a single-page application. Instead you need to use e.g. click handlers and call the `setRoute()` method yourself. | ||
<a name="attach-to-this"></a> | ||
@@ -665,6 +677,6 @@ ## Attach Properties To `this` | ||
* Buffer the request body and parse it according to the `Content-Type` header (usually `application/json` or `application/x-www-form-urlencoded`). | ||
* Buffer the request body and parse it according to the `Content-Type` header (usually `application/json` or `application/x-www-form-urlencoded`). | ||
* Stream the request body by manually calling `.pipe` or listening to the `data` and `end` events. | ||
By default `director.http.Router()` will attempt to parse either the `.chunks` or `.body` properties set on the request parameter passed to `router.dispatch(request, response, callback)`. The router instance will also wait for the `end` event before firing any routes. | ||
By default `director.http.Router()` will attempt to parse either the `.chunks` or `.body` properties set on the request parameter passed to `router.dispatch(request, response, callback)`. The router instance will also wait for the `end` event before firing any routes. | ||
@@ -675,3 +687,3 @@ **Default Behavior** | ||
var director = require('director'); | ||
var router = new director.http.Router(); | ||
@@ -682,3 +694,3 @@ | ||
// This will not work, because all of the data | ||
// events and the end event have already fired. | ||
// events and the end event have already fired. | ||
// | ||
@@ -696,3 +708,3 @@ this.req.on('data', function (chunk) { | ||
director = require('director'); | ||
var router = new director.http.Router(); | ||
@@ -705,3 +717,3 @@ | ||
}); | ||
router.dispatch(req, res, function (err) { | ||
@@ -712,7 +724,7 @@ if (err) { | ||
} | ||
console.log('Served ' + req.url); | ||
}); | ||
}); | ||
router.post('/', function () { | ||
@@ -724,3 +736,3 @@ this.res.writeHead(200, { 'Content-Type': 'application/json' }) | ||
**Streaming Support** | ||
**Streaming Support** | ||
@@ -736,4 +748,4 @@ If you wish to get access to the request stream before the `end` event is fired, you can pass the `{ stream: true }` options to the route. | ||
// | ||
// This will work because the route handler is invoked | ||
// immediately without waiting for the `end` event. | ||
// This will work because the route handler is invoked | ||
// immediately without waiting for the `end` event. | ||
// | ||
@@ -765,3 +777,3 @@ this.req.on('data', function (chunk) { | ||
Adds the `route` handler for the specified `method` and `path` within the [Routing Table](#routing-table). | ||
Adds the `route` handler for the specified `method` and `path` within the [Routing Table](#routing-table). | ||
@@ -795,3 +807,3 @@ ### path(path, routesFn) | ||
### getRoute([index]) | ||
* `index` {Number}: The hash value is divided by forward slashes, each section then has an index, if this is provided, only that section of the route will be returned. | ||
* `index` {Number}: The hash value is divided by forward slashes, each section then has an index, if this is provided, only that section of the route will be returned. | ||
@@ -801,6 +813,6 @@ Returns the entire route or just a section of it. | ||
### setRoute(route) | ||
* `route` {String}: Supply a route value, such as `home/stats`. | ||
* `route` {String}: Supply a route value, such as `home/stats`. | ||
Set the current route. | ||
### setRoute(start, length) | ||
@@ -807,0 +819,0 @@ * `start` {Number} - The position at which to start removing items. |
@@ -5,2 +5,9 @@ module("Director.js", { | ||
shared = {}; | ||
// Init needed keys earlier because of in HTML5 mode the route handler | ||
// is executed upon Router.init() and due to that setting shared.fired | ||
// in the param test of createTest is too late | ||
if (HTML5TEST) { | ||
shared.fired = []; | ||
shared.fired_count = 0; | ||
} | ||
}, | ||
@@ -15,3 +22,3 @@ teardown: function() { | ||
function createTest(name, config, use, test) { | ||
function createTest(name, config, use, test, initialRoute) { | ||
if (typeof use === 'function') { | ||
@@ -21,2 +28,17 @@ test = use; | ||
} | ||
if (HTML5TEST) { | ||
if (use === undefined) { | ||
use = {}; | ||
} | ||
if (use.run_handler_in_init === undefined) { | ||
use.run_handler_in_init = false; | ||
} | ||
use.html5history = true; | ||
} | ||
// Because of the use of setTimeout when defining onpopstate | ||
var innerTimeout = HTML5TEST === true ? 500 : 0; | ||
asyncTest(name, function() { | ||
@@ -31,19 +53,25 @@ setTimeout(function() { | ||
router.init(); | ||
router.init(initialRoute); | ||
test.call(context = { | ||
router: router, | ||
navigate: function(url, callback) { | ||
window.location.hash = url; | ||
setTimeout(function() { | ||
callback.call(context); | ||
}, 14); | ||
}, | ||
finish: function() { | ||
router.destroy(); | ||
start(); | ||
} | ||
}); | ||
setTimeout(function() { | ||
test.call(context = { | ||
router: router, | ||
navigate: function(url, callback) { | ||
if (HTML5TEST) { | ||
router.setRoute(url); | ||
} else { | ||
window.location.hash = url; | ||
} | ||
setTimeout(function() { | ||
callback.call(context); | ||
}, 14); | ||
}, | ||
finish: function() { | ||
router.destroy(); | ||
start(); | ||
} | ||
}) | ||
}, innerTimeout); | ||
}, 14); | ||
}); | ||
}; |
@@ -648,1 +648,48 @@ | ||
createTest('initializing with a default route should only result in one route handling', { | ||
'/': { | ||
on: function root() { | ||
if (!shared.init){ | ||
shared.init = 0; | ||
} | ||
shared.init++; | ||
} | ||
}, | ||
'/test': { | ||
on: function root() { | ||
if (!shared.test){ | ||
shared.test = 0; | ||
} | ||
shared.test++; | ||
} | ||
} | ||
}, function() { | ||
this.navigate('/test', function root() { | ||
equal(shared.init, 1); | ||
equal(shared.test, 1); | ||
this.finish(); | ||
}); | ||
}, | ||
null, | ||
'/'); | ||
createTest('changing the hash twice should call each route once', { | ||
'/hash1': { | ||
on: function root() { | ||
shared.fired.push('hash1'); | ||
} | ||
}, | ||
'/hash2': { | ||
on: function root() { | ||
shared.fired.push('hash2'); | ||
} | ||
} | ||
}, function() { | ||
shared.fired = []; | ||
this.navigate('/hash1', function(){}); | ||
this.navigate('/hash2', function(){ | ||
deepEqual(shared.fired, ['hash1', 'hash2']); | ||
this.finish(); | ||
}); | ||
} | ||
); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
284263
42
4822
816
1
6