flux-router-component
Advanced tools
Comparing version 0.5.0 to 0.5.1
@@ -50,2 +50,10 @@ /** | ||
/** | ||
* @method getState | ||
* @return {Object|null} The state object in history | ||
*/ | ||
getState: function () { | ||
return (this.win.history && this.win.history.state) || null; | ||
}, | ||
/** | ||
* Gets the path string, including the pathname and search query (if it exists). | ||
@@ -71,3 +79,3 @@ * @method getUrl | ||
win.history.pushState(state, title, url); | ||
} else { | ||
} else if (url) { | ||
win.location.href = url; | ||
@@ -88,3 +96,3 @@ } | ||
win.history.replaceState(state, title, url); | ||
} else { | ||
} else if (url) { | ||
win.location.replace(url); | ||
@@ -91,0 +99,0 @@ } |
@@ -5,2 +5,3 @@ /** | ||
*/ | ||
/*global window */ | ||
'use strict'; | ||
@@ -11,2 +12,3 @@ | ||
var History = require('./History'); | ||
var EVT_CLICK = 'click'; | ||
var EVT_PAGELOAD = 'pageload'; | ||
@@ -24,2 +26,9 @@ var EVT_POPSTATE = 'popstate'; | ||
function saveScrollPosition(e, history) { | ||
var historyState = (history.getState && history.getState()) || {}; | ||
historyState.scroll = {x: window.scrollX, y: window.scrollY}; | ||
debug('remember scroll position', historyState.scroll); | ||
history.replaceState(historyState); | ||
} | ||
RouterMixin = { | ||
@@ -33,2 +42,3 @@ componentDidMount: function() { | ||
self._history = ('function' === typeof self.props.historyCreator) ? self.props.historyCreator() : new History(); | ||
self._enableScroll = (self.props.enableScroll !== false); | ||
@@ -61,3 +71,3 @@ if (self.props.checkRouteOnPageLoad) { | ||
if (url !== self.state.route.url) { | ||
context.executeAction(navigateAction, {type: EVT_POPSTATE, url: url, params: e.state}); | ||
context.executeAction(navigateAction, {type: EVT_POPSTATE, url: url, params: (e.state && e.state.params)}); | ||
} | ||
@@ -67,2 +77,13 @@ } | ||
self._history.on(self._historyListener); | ||
if (self._enableScroll) { | ||
var scrollTimer; | ||
self._scrollListener = function (e) { | ||
if (scrollTimer) { | ||
window.clearTimeout(scrollTimer); | ||
} | ||
scrollTimer = window.setTimeout(saveScrollPosition.bind(self, e, self._history), 150); | ||
}; | ||
window.addEventListener('scroll', self._scrollListener); | ||
} | ||
}, | ||
@@ -72,2 +93,8 @@ componentWillUnmount: function() { | ||
this._historyListener = null; | ||
if (this._enableScroll) { | ||
window.removeEventListener('scroll', this._scrollListener); | ||
this._scrollListener = null; | ||
} | ||
this._history = null; | ||
@@ -84,4 +111,22 @@ }, | ||
var nav = newState.route.navigate; | ||
if (nav.type !== EVT_POPSTATE && nav.type !== EVT_PAGELOAD) { | ||
this._history.pushState(nav.params || null, null, newState.route.url); | ||
var historyState; | ||
switch (nav.type) { | ||
case EVT_CLICK: | ||
historyState = {params: (nav.params || {})}; | ||
if (this._enableScroll) { | ||
window.scrollTo(0, 0); | ||
historyState.scroll = {x: 0, y: 0}; | ||
debug('on click navigation, reset scroll position to (0, 0)'); | ||
} | ||
this._history.pushState(historyState, null, newState.route.url); | ||
break; | ||
case EVT_POPSTATE: | ||
if (this._enableScroll) { | ||
historyState = (this._history.getState && this._history.getState()) || {}; | ||
var scroll = (historyState && historyState.scroll) || {}; | ||
debug('on popstate navigation, restore scroll position to ', scroll); | ||
window.scrollTo(scroll.x || 0, scroll.y || 0); | ||
} | ||
break; | ||
} | ||
@@ -88,0 +133,0 @@ } |
{ | ||
"name": "flux-router-component", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"description": "Router-related React component and mixin for applications with Flux architecture", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
106
README.md
@@ -15,3 +15,13 @@ # flux-router-component | ||
### Example Usage | ||
Example of using `NavLink` with `href` property defined: | ||
Here are two examples of generating `NavLink` using `href` property, and using `routeName` property. Using `href` property is better than using `routeName`, because: | ||
* Using `href` makes your code more readible, as it shows exactly how the `href` is generated. | ||
* Using `routeName` assumes `this.prop.context` has a `makePath()` function, which will be used to generate the `href` from the `routeName` and `navParams` props. | ||
* Using `routeName` could be more limited, especially when it comes to query string and hash fragment, if the `makePath()` function does not support query string and hash fragment. | ||
#### Example of Using `href` Property (Recommended) | ||
If the url is static, or you can generate the url outside of `Navlink`, you can simply pass the url to `NavLink` as a prop. Here is an example: | ||
```js | ||
@@ -24,3 +34,3 @@ var NavLink = require('flux-router-component').NavLink; | ||
links, | ||
context = this.props.context; // this should have a router instance and an executeAction function | ||
context = this.props.context; // context should provide executeAction() | ||
pages = [ | ||
@@ -57,4 +67,59 @@ { | ||
We also have another more sophisticated example application, [routing](https://github.com/yahoo/flux-examples/tree/master/routing), that uses `NavLink` with `routeName` property defined. | ||
#### Example of Using `routeName` Property | ||
Before you continue with this example, you should know that you can always generate the url yourself outside of `NavLink` and pass it to `NavLink` as `href` prop just like the example above. Your code will be more straight-forward that way, and you will have more control over how to generate `href` (see more explanations in [the Example Usage section](#example-usage)). | ||
If you choose not to generate `href` yourself and the `context` prop you pass to `NavLink` provides `makePath(routeName, routeParams)`, you can also use the `routeName` prop (and the optional `navParams` prop). If the `href` prop is not present, `NavLink` will use `this.props.context.makePath(this.props.routeName, this.props.navParams)` to generate the `href` for the anchor element. The `navParams` prop is useful for dynamic routes. It should be a hash object containing the route parameters and their values. | ||
An example of such context is the `ComponentContext` provided by [fluxible-plugin-routr](https://github.com/yahoo/fluxible-plugin-routr/blob/master/lib/routr-plugin.js#L36), which is a plugin for [fluxible-app](https://github.com/yahoo/fluxible-app). We have a more sophisticated example application, [routing](https://github.com/yahoo/flux-examples/tree/master/routing), showing how everything works together. | ||
Here is a quick example code showcasing how to use `routeName` prop along with `navParams` prop: | ||
```js | ||
// assume routes are defined somewhere like this: | ||
// var routes = { | ||
// home: { | ||
// path: '/', | ||
// page: 'home' | ||
// }, | ||
// article: { | ||
// path: '/article/:id', | ||
// page: 'article' | ||
// } | ||
// }; | ||
var pages = [ | ||
{ | ||
routeName: 'home', | ||
text: 'Home' | ||
}, | ||
{ | ||
routeName: 'article', | ||
routeParams: { | ||
id: 'a' | ||
} | ||
text: 'Article A' | ||
} | ||
]; | ||
var Nav = React.createClass({ | ||
render: function () { | ||
var context = this.props.context; // context should provide executeAction() and makePath() | ||
var links = pages.map(function (page) { | ||
return ( | ||
<li className="navItem"> | ||
<NavLink routeName={page.routeName} navParams={page.routeParams} context={context}> | ||
{page.text} | ||
</NavLink> | ||
</li> | ||
); | ||
}); | ||
return ( | ||
<ul className="nav"> | ||
{links} | ||
</ul> | ||
); | ||
} | ||
}); | ||
``` | ||
## RouterMixin | ||
@@ -65,4 +130,4 @@ `RouterMixin` is a React mixin to be used by application's top level React component to: | ||
* execute navigate action and then dispatch `CHANGE_ROUTE_START` and `CHANGE_ROUTE_SUCCESS` or `CHANGE_ROUTE_FAILURE` events via flux dispatcher on window `popstate` events | ||
* [manage scroll position](#scroll-position-management) when navigating between pages | ||
### Example Usage | ||
@@ -128,4 +193,8 @@ ```js | ||
return new HistoryWithHash({ | ||
useHashRoute: true, // optional | ||
hashRouteTransformer: { // optional transformer for custom hash route syntax | ||
// optional. Defaults to true if browser does not support pushState; false otherwise. | ||
useHashRoute: true, | ||
// optional. Defaults to '/'. Used when url has no hash fragment | ||
defaultHashRoute: '/default', | ||
// optional. Transformer for custom hash route syntax | ||
hashRouteTransformer: { | ||
transform: function (original) { | ||
@@ -157,2 +226,3 @@ // transform url hash fragment from '/new/path' to 'new-path' | ||
* getUrl() | ||
* getState() | ||
* pushState(state, title, url) | ||
@@ -181,3 +251,27 @@ * replaceState(state, title, url) | ||
## Scroll Position Management | ||
`RouterMixin` has a built-in mechanism for managing scroll position upon page navigation, for modern browsers that support native history state: | ||
* reset scroll position to `(0, 0)` when user clicks on a link and navigates to a new page, and | ||
* restore scroll position to last visited state when user clicks forward and back buttons to navigate between pages. | ||
If you want to disable this behavior, you can set `enableScroll` prop to `false` for `RouterMixin`. This is an example of how it can be done: | ||
```js | ||
var RouterMixin = require('flux-router-component').RouterMixin; | ||
var Application = React.createClass({ | ||
mixins: [RouterMixin], | ||
... | ||
}); | ||
var appComponent = Application({ | ||
... | ||
enableScroll: false | ||
}); | ||
``` | ||
## Polyfills | ||
@@ -184,0 +278,0 @@ `addEventListener` and `removeEventListener` polyfills are provided by: |
@@ -16,2 +16,4 @@ /** | ||
* is not available in the window object's history object; false otherwise. | ||
* @param {String} [options.defaultHashRoute='/'] Only used when options.useHashRoute is enabled and | ||
the location url does not have any hash fragment. | ||
* @param {Object} [options.hashRouteTransformer] A custom transformer can be provided | ||
@@ -40,2 +42,3 @@ * to transform the hash to the desired syntax. | ||
} | ||
this._defaultHashRoute = options.defaultHashRoute || '/'; | ||
@@ -71,7 +74,7 @@ // allow custom syntax for hash | ||
* Returns the hash fragment in current window location. | ||
* @method _getHash | ||
* @method _getHashRoute | ||
* @return {String} The hash fragment string (without the # prefix). | ||
* @private | ||
*/ | ||
_getHash: function () { | ||
_getHashRoute: function () { | ||
var hash = this.win.location.hash, | ||
@@ -81,3 +84,3 @@ transformer = this._hashRouteTransformer; | ||
// remove the '#' prefix | ||
hash = hash.substring(1) || ''; | ||
hash = hash.substring(1) || this._defaultHashRoute; | ||
@@ -88,2 +91,10 @@ return (transformer && transformer.reverse) ? transformer.reverse(hash) : hash; | ||
/** | ||
* @method getState | ||
* @return {Object|null} The state object in history | ||
*/ | ||
getState: function () { | ||
return (this.win.history && this.win.history.state) || null; | ||
}, | ||
/** | ||
* Gets the path string (or hash fragment if the history object is | ||
@@ -100,3 +111,3 @@ * configured to use hash for routing), | ||
if (this._useHashRoute) { | ||
return this._getHash() || path; | ||
return this._getHashRoute(); | ||
} | ||
@@ -126,4 +137,5 @@ return path; | ||
if (this._hasPushState) { | ||
history.pushState(state, title, location.pathname + location.search + hash); | ||
} else { | ||
url = hash ? location.pathname + location.search + hash : null; | ||
history.pushState(state, title, url); | ||
} else if (hash) { | ||
location.hash = hash; | ||
@@ -134,3 +146,3 @@ } | ||
history.pushState(state, title, url); | ||
} else { | ||
} else if (url) { | ||
location.href = url; | ||
@@ -160,6 +172,7 @@ } | ||
} | ||
url = location.pathname + location.search + hash; | ||
if (this._hasPushState) { | ||
url = hash ? (location.pathname + location.search + hash) : null; | ||
history.replaceState(state, title, url); | ||
} else { | ||
} else if (url) { | ||
url = location.pathname + location.search + hash; | ||
location.replace(url); | ||
@@ -170,3 +183,3 @@ } | ||
history.replaceState(state, title, url); | ||
} else { | ||
} else if (url) { | ||
location.replace(url); | ||
@@ -173,0 +186,0 @@ } |
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
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
35965
503
291