├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── UPGRADE_GUIDE.md ├── bower.json ├── build ├── global │ ├── ReactRouter.js │ └── ReactRouter.min.js └── npm │ ├── README.md │ ├── lib │ ├── Cancellation.js │ ├── History.js │ ├── Match.js │ ├── Navigation.js │ ├── PathUtils.js │ ├── PropTypes.js │ ├── Redirect.js │ ├── Route.js │ ├── ScrollHistory.js │ ├── State.js │ ├── TestUtils.js │ ├── Transition.js │ ├── actions │ │ └── LocationActions.js │ ├── behaviors │ │ ├── ImitateBrowserBehavior.js │ │ └── ScrollToTopBehavior.js │ ├── components │ │ ├── ContextWrapper.js │ │ ├── DefaultRoute.js │ │ ├── Link.js │ │ ├── NotFoundRoute.js │ │ ├── Redirect.js │ │ ├── Route.js │ │ └── RouteHandler.js │ ├── createRouter.js │ ├── createRoutesFromReactChildren.js │ ├── getWindowScrollPosition.js │ ├── index.js │ ├── isReactChildren.js │ ├── locations │ │ ├── HashLocation.js │ │ ├── HistoryLocation.js │ │ ├── RefreshLocation.js │ │ ├── StaticLocation.js │ │ └── TestLocation.js │ ├── runRouter.js │ └── supportsHistory.js │ └── package.json ├── docs ├── api │ ├── Location.md │ ├── README.md │ ├── RouterContext.md │ ├── Transition.md │ ├── components │ │ ├── DefaultRoute.md │ │ ├── Link.md │ │ ├── NotFoundRoute.md │ │ ├── Redirect.md │ │ ├── Route.md │ │ └── RouteHandler.md │ ├── create.md │ ├── mixins │ │ ├── Navigation.md │ │ └── State.md │ └── run.md └── guides │ ├── flux.md │ ├── overview.md │ ├── path-matching.md │ ├── server-rendering.md │ └── testing.md ├── examples ├── README.md ├── animations │ ├── app.css │ ├── app.js │ └── index.html ├── async-data │ ├── app.css │ ├── app.js │ └── index.html ├── auth-flow │ ├── app.js │ └── index.html ├── data-flow │ ├── app.css │ ├── app.js │ └── index.html ├── dynamic-segments │ ├── app.js │ └── index.html ├── global.css ├── index.html ├── master-detail │ ├── ContactStore.js │ ├── app.css │ ├── app.js │ └── index.html ├── partial-app-loading │ ├── app.js │ ├── dashboard.js │ ├── inbox.js │ └── index.html ├── query-params │ ├── app.js │ └── index.html ├── rx │ ├── app.js │ └── index.html ├── shared-root │ ├── app.js │ └── index.html ├── sidebar │ ├── app.css │ ├── app.js │ ├── data.js │ └── index.html ├── simple-master-detail │ ├── app.css │ ├── app.js │ └── index.html ├── transitions │ ├── app.js │ └── index.html └── webpack.config.js ├── karma.conf.js ├── modules ├── Cancellation.js ├── History.js ├── Match.js ├── Navigation.js ├── PathUtils.js ├── PropTypes.js ├── Redirect.js ├── Route.js ├── ScrollHistory.js ├── State.js ├── TestUtils.js ├── Transition.js ├── __tests__ │ ├── History-test.js │ ├── PathUtils-test.js │ ├── Router-test.js │ ├── Routing-test.js │ └── State-test.js ├── actions │ └── LocationActions.js ├── behaviors │ ├── ImitateBrowserBehavior.js │ └── ScrollToTopBehavior.js ├── components │ ├── ContextWrapper.js │ ├── DefaultRoute.js │ ├── Link.js │ ├── NotFoundRoute.js │ ├── Redirect.js │ ├── Route.js │ ├── RouteHandler.js │ └── __tests__ │ │ ├── DefaultRoute-test.js │ │ ├── Link-test.js │ │ ├── NotFoundRoute-test.js │ │ ├── Redirect-test.js │ │ ├── Route-test.js │ │ └── RouteHandler-test.js ├── createRouter.js ├── createRoutesFromReactChildren.js ├── getWindowScrollPosition.js ├── index.js ├── isReactChildren.js ├── locations │ ├── HashLocation.js │ ├── HistoryLocation.js │ ├── RefreshLocation.js │ ├── StaticLocation.js │ ├── TestLocation.js │ └── __tests__ │ │ └── HashLocation-test.js ├── runRouter.js └── supportsHistory.js ├── package.json ├── scripts ├── preview-release.sh └── release.sh ├── tests.webpack.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | examples/**/*-bundle.js 2 | node_modules 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | build 2 | examples/**/*-bundle.js 3 | node_modules 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "asi": true, 3 | "eqnull": true, 4 | "esnext": true, 5 | "unused": "vars", 6 | "predef": [ "-Promise" ] 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - export DISPLAY=:99.0 6 | - sh -e /etc/init.d/xvfb start 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Tests 2 | 3 | All commits that fix bugs or add features need a test. 4 | 5 | `` Do not merge code without tests.`` 6 | 7 | ### Commit Subjects for Public API Changes 8 | 9 | If your patch **changes the API or fixes a bug** please use one of the 10 | following prefixes in your commit subject: 11 | 12 | - `[fixed] ...` 13 | - `[changed] ...` 14 | - `[added] ...` 15 | - `[removed] ...` 16 | 17 | That ensures the subject line of your commit makes it into the 18 | auto-generated changelog. Do not use these tags if your change doesn't 19 | fix a bug and doesn't change the public API. 20 | 21 | Commits with changed, added, or removed, should probably be reviewed by 22 | another collaborator. 23 | 24 | #### When using `[changed]` or `[removed]`... 25 | 26 | Please include an upgrade path with example code in the commit message. 27 | If it doesn't make sense to do this, then it doesn't make sense to use 28 | `[changed]` or `[removed]` :) 29 | 30 | ### Docs 31 | 32 | Please update the docs with any API changes, the code and docs should 33 | always be in sync. 34 | 35 | ### Development 36 | 37 | - `npm test` will fire up a karma test runner and watch for changes 38 | - `npm run examples` fires up a webpack dev server that will watch 39 | for changes and build the examples 40 | 41 | ### Build 42 | 43 | Please do not include the output of `scripts/build` in your commits, we 44 | only do this when we release. (Also, you probably don't need to build 45 | anyway unless you are fixing something around our global build.) 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ryan Florence, Michael Jackson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-router", 3 | "version": "0.13.1", 4 | "description": "A complete routing library for React.js", 5 | "main": "build/global/ReactRouter.js", 6 | "homepage": "https://github.com/rackt/react-router", 7 | "authors": [ 8 | "Ryan Florence", 9 | "Michael Jackson" 10 | ], 11 | "keywords": [ 12 | "react", 13 | "router" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "specs", 21 | "modules", 22 | "examples", 23 | "script", 24 | "CONTRIBUTING.md", 25 | "karma.conf.js", 26 | "package.json", 27 | "webpack.config.js" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /build/npm/lib/Cancellation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Represents a cancellation caused by navigating away 5 | * before the previous transition has fully resolved. 6 | */ 7 | function Cancellation() {} 8 | 9 | module.exports = Cancellation; -------------------------------------------------------------------------------- /build/npm/lib/History.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var invariant = require("react/lib/invariant"); 4 | var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; 5 | 6 | var History = { 7 | 8 | /** 9 | * The current number of entries in the history. 10 | * 11 | * Note: This property is read-only. 12 | */ 13 | length: 1, 14 | 15 | /** 16 | * Sends the browser back one entry in the history. 17 | */ 18 | back: function back() { 19 | invariant(canUseDOM, "Cannot use History.back without a DOM"); 20 | 21 | // Do this first so that History.length will 22 | // be accurate in location change listeners. 23 | History.length -= 1; 24 | 25 | window.history.back(); 26 | } 27 | 28 | }; 29 | 30 | module.exports = History; -------------------------------------------------------------------------------- /build/npm/lib/Match.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | /* jshint -W084 */ 8 | var PathUtils = require("./PathUtils"); 9 | 10 | function deepSearch(route, pathname, query) { 11 | // Check the subtree first to find the most deeply-nested match. 12 | var childRoutes = route.childRoutes; 13 | if (childRoutes) { 14 | var match, childRoute; 15 | for (var i = 0, len = childRoutes.length; i < len; ++i) { 16 | childRoute = childRoutes[i]; 17 | 18 | if (childRoute.isDefault || childRoute.isNotFound) continue; // Check these in order later. 19 | 20 | if (match = deepSearch(childRoute, pathname, query)) { 21 | // A route in the subtree matched! Add this route and we're done. 22 | match.routes.unshift(route); 23 | return match; 24 | } 25 | } 26 | } 27 | 28 | // No child routes matched; try the default route. 29 | var defaultRoute = route.defaultRoute; 30 | if (defaultRoute && (params = PathUtils.extractParams(defaultRoute.path, pathname))) { 31 | return new Match(pathname, params, query, [route, defaultRoute]); 32 | } // Does the "not found" route match? 33 | var notFoundRoute = route.notFoundRoute; 34 | if (notFoundRoute && (params = PathUtils.extractParams(notFoundRoute.path, pathname))) { 35 | return new Match(pathname, params, query, [route, notFoundRoute]); 36 | } // Last attempt: check this route. 37 | var params = PathUtils.extractParams(route.path, pathname); 38 | if (params) { 39 | return new Match(pathname, params, query, [route]); 40 | }return null; 41 | } 42 | 43 | var Match = (function () { 44 | function Match(pathname, params, query, routes) { 45 | _classCallCheck(this, Match); 46 | 47 | this.pathname = pathname; 48 | this.params = params; 49 | this.query = query; 50 | this.routes = routes; 51 | } 52 | 53 | _createClass(Match, null, { 54 | findMatch: { 55 | 56 | /** 57 | * Attempts to match depth-first a route in the given route's 58 | * subtree against the given path and returns the match if it 59 | * succeeds, null if no match can be made. 60 | */ 61 | 62 | value: function findMatch(routes, path) { 63 | var pathname = PathUtils.withoutQuery(path); 64 | var query = PathUtils.extractQuery(path); 65 | var match = null; 66 | 67 | for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query); 68 | 69 | return match; 70 | } 71 | } 72 | }); 73 | 74 | return Match; 75 | })(); 76 | 77 | module.exports = Match; -------------------------------------------------------------------------------- /build/npm/lib/Navigation.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var warning = require("react/lib/warning"); 4 | var PropTypes = require("./PropTypes"); 5 | 6 | function deprecatedMethod(routerMethodName, fn) { 7 | return function () { 8 | warning(false, "Router.Navigation is deprecated. Please use this.context.router." + routerMethodName + "() instead"); 9 | 10 | return fn.apply(this, arguments); 11 | }; 12 | } 13 | 14 | /** 15 | * A mixin for components that modify the URL. 16 | * 17 | * Example: 18 | * 19 | * var MyLink = React.createClass({ 20 | * mixins: [ Router.Navigation ], 21 | * handleClick(event) { 22 | * event.preventDefault(); 23 | * this.transitionTo('aRoute', { the: 'params' }, { the: 'query' }); 24 | * }, 25 | * render() { 26 | * return ( 27 | * Click me! 28 | * ); 29 | * } 30 | * }); 31 | */ 32 | var Navigation = { 33 | 34 | contextTypes: { 35 | router: PropTypes.router.isRequired 36 | }, 37 | 38 | /** 39 | * Returns an absolute URL path created from the given route 40 | * name, URL parameters, and query values. 41 | */ 42 | makePath: deprecatedMethod("makePath", function (to, params, query) { 43 | return this.context.router.makePath(to, params, query); 44 | }), 45 | 46 | /** 47 | * Returns a string that may safely be used as the href of a 48 | * link to the route with the given name. 49 | */ 50 | makeHref: deprecatedMethod("makeHref", function (to, params, query) { 51 | return this.context.router.makeHref(to, params, query); 52 | }), 53 | 54 | /** 55 | * Transitions to the URL specified in the arguments by pushing 56 | * a new URL onto the history stack. 57 | */ 58 | transitionTo: deprecatedMethod("transitionTo", function (to, params, query) { 59 | this.context.router.transitionTo(to, params, query); 60 | }), 61 | 62 | /** 63 | * Transitions to the URL specified in the arguments by replacing 64 | * the current URL in the history stack. 65 | */ 66 | replaceWith: deprecatedMethod("replaceWith", function (to, params, query) { 67 | this.context.router.replaceWith(to, params, query); 68 | }), 69 | 70 | /** 71 | * Transitions to the previous URL. 72 | */ 73 | goBack: deprecatedMethod("goBack", function () { 74 | return this.context.router.goBack(); 75 | }) 76 | 77 | }; 78 | 79 | module.exports = Navigation; -------------------------------------------------------------------------------- /build/npm/lib/PropTypes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var assign = require("react/lib/Object.assign"); 4 | var ReactPropTypes = require("react").PropTypes; 5 | var Route = require("./Route"); 6 | 7 | var PropTypes = assign({}, ReactPropTypes, { 8 | 9 | /** 10 | * Indicates that a prop should be falsy. 11 | */ 12 | falsy: function falsy(props, propName, componentName) { 13 | if (props[propName]) { 14 | return new Error("<" + componentName + "> may not have a \"" + propName + "\" prop"); 15 | } 16 | }, 17 | 18 | /** 19 | * Indicates that a prop should be a Route object. 20 | */ 21 | route: ReactPropTypes.instanceOf(Route), 22 | 23 | /** 24 | * Indicates that a prop should be a Router object. 25 | */ 26 | //router: ReactPropTypes.instanceOf(Router) // TODO 27 | router: ReactPropTypes.func 28 | 29 | }); 30 | 31 | module.exports = PropTypes; -------------------------------------------------------------------------------- /build/npm/lib/Redirect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Encapsulates a redirect to the given route. 5 | */ 6 | function Redirect(to, params, query) { 7 | this.to = to; 8 | this.params = params; 9 | this.query = query; 10 | } 11 | 12 | module.exports = Redirect; -------------------------------------------------------------------------------- /build/npm/lib/ScrollHistory.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var invariant = require("react/lib/invariant"); 4 | var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; 5 | var getWindowScrollPosition = require("./getWindowScrollPosition"); 6 | 7 | function shouldUpdateScroll(state, prevState) { 8 | if (!prevState) { 9 | return true; 10 | } // Don't update scroll position when only the query has changed. 11 | if (state.pathname === prevState.pathname) { 12 | return false; 13 | }var routes = state.routes; 14 | var prevRoutes = prevState.routes; 15 | 16 | var sharedAncestorRoutes = routes.filter(function (route) { 17 | return prevRoutes.indexOf(route) !== -1; 18 | }); 19 | 20 | return !sharedAncestorRoutes.some(function (route) { 21 | return route.ignoreScrollBehavior; 22 | }); 23 | } 24 | 25 | /** 26 | * Provides the router with the ability to manage window scroll position 27 | * according to its scroll behavior. 28 | */ 29 | var ScrollHistory = { 30 | 31 | statics: { 32 | 33 | /** 34 | * Records curent scroll position as the last known position for the given URL path. 35 | */ 36 | recordScrollPosition: function recordScrollPosition(path) { 37 | if (!this.scrollHistory) this.scrollHistory = {}; 38 | 39 | this.scrollHistory[path] = getWindowScrollPosition(); 40 | }, 41 | 42 | /** 43 | * Returns the last known scroll position for the given URL path. 44 | */ 45 | getScrollPosition: function getScrollPosition(path) { 46 | if (!this.scrollHistory) this.scrollHistory = {}; 47 | 48 | return this.scrollHistory[path] || null; 49 | } 50 | 51 | }, 52 | 53 | componentWillMount: function componentWillMount() { 54 | invariant(this.constructor.getScrollBehavior() == null || canUseDOM, "Cannot use scroll behavior without a DOM"); 55 | }, 56 | 57 | componentDidMount: function componentDidMount() { 58 | this._updateScroll(); 59 | }, 60 | 61 | componentDidUpdate: function componentDidUpdate(prevProps, prevState) { 62 | this._updateScroll(prevState); 63 | }, 64 | 65 | _updateScroll: function _updateScroll(prevState) { 66 | if (!shouldUpdateScroll(this.state, prevState)) { 67 | return; 68 | }var scrollBehavior = this.constructor.getScrollBehavior(); 69 | 70 | if (scrollBehavior) scrollBehavior.updateScrollPosition(this.constructor.getScrollPosition(this.state.path), this.state.action); 71 | } 72 | 73 | }; 74 | 75 | module.exports = ScrollHistory; -------------------------------------------------------------------------------- /build/npm/lib/State.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var warning = require("react/lib/warning"); 4 | var PropTypes = require("./PropTypes"); 5 | 6 | function deprecatedMethod(routerMethodName, fn) { 7 | return function () { 8 | warning(false, "Router.State is deprecated. Please use this.context.router." + routerMethodName + "() instead"); 9 | 10 | return fn.apply(this, arguments); 11 | }; 12 | } 13 | 14 | /** 15 | * A mixin for components that need to know the path, routes, URL 16 | * params and query that are currently active. 17 | * 18 | * Example: 19 | * 20 | * var AboutLink = React.createClass({ 21 | * mixins: [ Router.State ], 22 | * render() { 23 | * var className = this.props.className; 24 | * 25 | * if (this.isActive('about')) 26 | * className += ' is-active'; 27 | * 28 | * return React.DOM.a({ className: className }, this.props.children); 29 | * } 30 | * }); 31 | */ 32 | var State = { 33 | 34 | contextTypes: { 35 | router: PropTypes.router.isRequired 36 | }, 37 | 38 | /** 39 | * Returns the current URL path. 40 | */ 41 | getPath: deprecatedMethod("getCurrentPath", function () { 42 | return this.context.router.getCurrentPath(); 43 | }), 44 | 45 | /** 46 | * Returns the current URL path without the query string. 47 | */ 48 | getPathname: deprecatedMethod("getCurrentPathname", function () { 49 | return this.context.router.getCurrentPathname(); 50 | }), 51 | 52 | /** 53 | * Returns an object of the URL params that are currently active. 54 | */ 55 | getParams: deprecatedMethod("getCurrentParams", function () { 56 | return this.context.router.getCurrentParams(); 57 | }), 58 | 59 | /** 60 | * Returns an object of the query params that are currently active. 61 | */ 62 | getQuery: deprecatedMethod("getCurrentQuery", function () { 63 | return this.context.router.getCurrentQuery(); 64 | }), 65 | 66 | /** 67 | * Returns an array of the routes that are currently active. 68 | */ 69 | getRoutes: deprecatedMethod("getCurrentRoutes", function () { 70 | return this.context.router.getCurrentRoutes(); 71 | }), 72 | 73 | /** 74 | * A helper method to determine if a given route, params, and query 75 | * are active. 76 | */ 77 | isActive: deprecatedMethod("isActive", function (to, params, query) { 78 | return this.context.router.isActive(to, params, query); 79 | }) 80 | 81 | }; 82 | 83 | module.exports = State; -------------------------------------------------------------------------------- /build/npm/lib/TestUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var React = require("react"); 4 | var RouteHandler = require("./components/RouteHandler"); 5 | var PropTypes = require("./PropTypes"); 6 | 7 | exports.Nested = React.createClass({ 8 | displayName: "Nested", 9 | 10 | render: function render() { 11 | return React.createElement( 12 | "div", 13 | null, 14 | React.createElement( 15 | "h1", 16 | { className: "Nested" }, 17 | "Nested" 18 | ), 19 | React.createElement(RouteHandler, null) 20 | ); 21 | } 22 | }); 23 | 24 | exports.Foo = React.createClass({ 25 | displayName: "Foo", 26 | 27 | render: function render() { 28 | return React.createElement( 29 | "div", 30 | { className: "Foo" }, 31 | "Foo" 32 | ); 33 | } 34 | }); 35 | 36 | exports.Bar = React.createClass({ 37 | displayName: "Bar", 38 | 39 | render: function render() { 40 | return React.createElement( 41 | "div", 42 | { className: "Bar" }, 43 | "Bar" 44 | ); 45 | } 46 | }); 47 | 48 | exports.Baz = React.createClass({ 49 | displayName: "Baz", 50 | 51 | render: function render() { 52 | return React.createElement( 53 | "div", 54 | { className: "Baz" }, 55 | "Baz" 56 | ); 57 | } 58 | }); 59 | 60 | exports.Async = React.createClass({ 61 | displayName: "Async", 62 | 63 | statics: { 64 | delay: 10, 65 | 66 | willTransitionTo: function willTransitionTo(transition, params, query, callback) { 67 | setTimeout(callback, exports.Async.delay); 68 | } 69 | }, 70 | 71 | render: function render() { 72 | return React.createElement( 73 | "div", 74 | { className: "Async" }, 75 | "Async" 76 | ); 77 | } 78 | }); 79 | 80 | exports.RedirectToFoo = React.createClass({ 81 | displayName: "RedirectToFoo", 82 | 83 | statics: { 84 | willTransitionTo: function willTransitionTo(transition) { 85 | transition.redirect("/foo"); 86 | } 87 | }, 88 | 89 | render: function render() { 90 | return null; 91 | } 92 | }); 93 | 94 | exports.RedirectToFooAsync = React.createClass({ 95 | displayName: "RedirectToFooAsync", 96 | 97 | statics: { 98 | delay: 10, 99 | 100 | willTransitionTo: function willTransitionTo(transition, params, query, callback) { 101 | setTimeout(function () { 102 | transition.redirect("/foo"); 103 | callback(); 104 | }, exports.RedirectToFooAsync.delay); 105 | } 106 | }, 107 | 108 | render: function render() { 109 | return null; 110 | } 111 | }); 112 | 113 | exports.Abort = React.createClass({ 114 | displayName: "Abort", 115 | 116 | statics: { 117 | willTransitionTo: function willTransitionTo(transition) { 118 | transition.abort(); 119 | } 120 | }, 121 | 122 | render: function render() { 123 | return null; 124 | } 125 | }); 126 | 127 | exports.AbortAsync = React.createClass({ 128 | displayName: "AbortAsync", 129 | 130 | statics: { 131 | delay: 10, 132 | 133 | willTransitionTo: function willTransitionTo(transition, params, query, callback) { 134 | setTimeout(function () { 135 | transition.abort(); 136 | callback(); 137 | }, exports.AbortAsync.delay); 138 | } 139 | }, 140 | 141 | render: function render() { 142 | return null; 143 | } 144 | }); 145 | 146 | exports.EchoFooProp = React.createClass({ 147 | displayName: "EchoFooProp", 148 | 149 | render: function render() { 150 | return React.createElement( 151 | "div", 152 | null, 153 | this.props.foo 154 | ); 155 | } 156 | }); 157 | 158 | exports.EchoBarParam = React.createClass({ 159 | displayName: "EchoBarParam", 160 | 161 | contextTypes: { 162 | router: PropTypes.router.isRequired 163 | }, 164 | render: function render() { 165 | return React.createElement( 166 | "div", 167 | { className: "EchoBarParam" }, 168 | this.context.router.getCurrentParams().bar 169 | ); 170 | } 171 | }); -------------------------------------------------------------------------------- /build/npm/lib/Transition.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* jshint -W058 */ 4 | 5 | var Cancellation = require("./Cancellation"); 6 | var Redirect = require("./Redirect"); 7 | 8 | /** 9 | * Encapsulates a transition to a given path. 10 | * 11 | * The willTransitionTo and willTransitionFrom handlers receive 12 | * an instance of this class as their first argument. 13 | */ 14 | function Transition(path, retry) { 15 | this.path = path; 16 | this.abortReason = null; 17 | // TODO: Change this to router.retryTransition(transition) 18 | this.retry = retry.bind(this); 19 | } 20 | 21 | Transition.prototype.abort = function (reason) { 22 | if (this.abortReason == null) this.abortReason = reason || "ABORT"; 23 | }; 24 | 25 | Transition.prototype.redirect = function (to, params, query) { 26 | this.abort(new Redirect(to, params, query)); 27 | }; 28 | 29 | Transition.prototype.cancel = function () { 30 | this.abort(new Cancellation()); 31 | }; 32 | 33 | Transition.from = function (transition, routes, components, callback) { 34 | routes.reduce(function (callback, route, index) { 35 | return function (error) { 36 | if (error || transition.abortReason) { 37 | callback(error); 38 | } else if (route.onLeave) { 39 | try { 40 | route.onLeave(transition, components[index], callback); 41 | 42 | // If there is no callback in the argument list, call it automatically. 43 | if (route.onLeave.length < 3) callback(); 44 | } catch (e) { 45 | callback(e); 46 | } 47 | } else { 48 | callback(); 49 | } 50 | }; 51 | }, callback)(); 52 | }; 53 | 54 | Transition.to = function (transition, routes, params, query, callback) { 55 | routes.reduceRight(function (callback, route) { 56 | return function (error) { 57 | if (error || transition.abortReason) { 58 | callback(error); 59 | } else if (route.onEnter) { 60 | try { 61 | route.onEnter(transition, params, query, callback); 62 | 63 | // If there is no callback in the argument list, call it automatically. 64 | if (route.onEnter.length < 4) callback(); 65 | } catch (e) { 66 | callback(e); 67 | } 68 | } else { 69 | callback(); 70 | } 71 | }; 72 | }, callback)(); 73 | }; 74 | 75 | module.exports = Transition; -------------------------------------------------------------------------------- /build/npm/lib/actions/LocationActions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Actions that modify the URL. 5 | */ 6 | var LocationActions = { 7 | 8 | /** 9 | * Indicates a new location is being pushed to the history stack. 10 | */ 11 | PUSH: "push", 12 | 13 | /** 14 | * Indicates the current location should be replaced. 15 | */ 16 | REPLACE: "replace", 17 | 18 | /** 19 | * Indicates the most recent entry should be removed from the history stack. 20 | */ 21 | POP: "pop" 22 | 23 | }; 24 | 25 | module.exports = LocationActions; -------------------------------------------------------------------------------- /build/npm/lib/behaviors/ImitateBrowserBehavior.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var LocationActions = require("../actions/LocationActions"); 4 | 5 | /** 6 | * A scroll behavior that attempts to imitate the default behavior 7 | * of modern browsers. 8 | */ 9 | var ImitateBrowserBehavior = { 10 | 11 | updateScrollPosition: function updateScrollPosition(position, actionType) { 12 | switch (actionType) { 13 | case LocationActions.PUSH: 14 | case LocationActions.REPLACE: 15 | window.scrollTo(0, 0); 16 | break; 17 | case LocationActions.POP: 18 | if (position) { 19 | window.scrollTo(position.x, position.y); 20 | } else { 21 | window.scrollTo(0, 0); 22 | } 23 | break; 24 | } 25 | } 26 | 27 | }; 28 | 29 | module.exports = ImitateBrowserBehavior; -------------------------------------------------------------------------------- /build/npm/lib/behaviors/ScrollToTopBehavior.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * A scroll behavior that always scrolls to the top of the page 5 | * after a transition. 6 | */ 7 | var ScrollToTopBehavior = { 8 | 9 | updateScrollPosition: function updateScrollPosition() { 10 | window.scrollTo(0, 0); 11 | } 12 | 13 | }; 14 | 15 | module.exports = ScrollToTopBehavior; -------------------------------------------------------------------------------- /build/npm/lib/components/ContextWrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 6 | 7 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 8 | 9 | /** 10 | * This component is necessary to get around a context warning 11 | * present in React 0.13.0. It sovles this by providing a separation 12 | * between the "owner" and "parent" contexts. 13 | */ 14 | 15 | var React = require("react"); 16 | 17 | var ContextWrapper = (function (_React$Component) { 18 | function ContextWrapper() { 19 | _classCallCheck(this, ContextWrapper); 20 | 21 | if (_React$Component != null) { 22 | _React$Component.apply(this, arguments); 23 | } 24 | } 25 | 26 | _inherits(ContextWrapper, _React$Component); 27 | 28 | _createClass(ContextWrapper, { 29 | render: { 30 | value: function render() { 31 | return this.props.children; 32 | } 33 | } 34 | }); 35 | 36 | return ContextWrapper; 37 | })(React.Component); 38 | 39 | module.exports = ContextWrapper; -------------------------------------------------------------------------------- /build/npm/lib/components/DefaultRoute.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | var PropTypes = require("../PropTypes"); 8 | var RouteHandler = require("./RouteHandler"); 9 | var Route = require("./Route"); 10 | 11 | /** 12 | * A component is a special kind of that 13 | * renders when its parent matches but none of its siblings do. 14 | * Only one such route may be used at any given level in the 15 | * route hierarchy. 16 | */ 17 | 18 | var DefaultRoute = (function (_Route) { 19 | function DefaultRoute() { 20 | _classCallCheck(this, DefaultRoute); 21 | 22 | if (_Route != null) { 23 | _Route.apply(this, arguments); 24 | } 25 | } 26 | 27 | _inherits(DefaultRoute, _Route); 28 | 29 | return DefaultRoute; 30 | })(Route); 31 | 32 | // TODO: Include these in the above class definition 33 | // once we can use ES7 property initializers. 34 | // https://github.com/babel/babel/issues/619 35 | 36 | DefaultRoute.propTypes = { 37 | name: PropTypes.string, 38 | path: PropTypes.falsy, 39 | children: PropTypes.falsy, 40 | handler: PropTypes.func.isRequired 41 | }; 42 | 43 | DefaultRoute.defaultProps = { 44 | handler: RouteHandler 45 | }; 46 | 47 | module.exports = DefaultRoute; -------------------------------------------------------------------------------- /build/npm/lib/components/NotFoundRoute.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | var PropTypes = require("../PropTypes"); 8 | var RouteHandler = require("./RouteHandler"); 9 | var Route = require("./Route"); 10 | 11 | /** 12 | * A is a special kind of that 13 | * renders when the beginning of its parent's path matches 14 | * but none of its siblings do, including any . 15 | * Only one such route may be used at any given level in the 16 | * route hierarchy. 17 | */ 18 | 19 | var NotFoundRoute = (function (_Route) { 20 | function NotFoundRoute() { 21 | _classCallCheck(this, NotFoundRoute); 22 | 23 | if (_Route != null) { 24 | _Route.apply(this, arguments); 25 | } 26 | } 27 | 28 | _inherits(NotFoundRoute, _Route); 29 | 30 | return NotFoundRoute; 31 | })(Route); 32 | 33 | // TODO: Include these in the above class definition 34 | // once we can use ES7 property initializers. 35 | // https://github.com/babel/babel/issues/619 36 | 37 | NotFoundRoute.propTypes = { 38 | name: PropTypes.string, 39 | path: PropTypes.falsy, 40 | children: PropTypes.falsy, 41 | handler: PropTypes.func.isRequired 42 | }; 43 | 44 | NotFoundRoute.defaultProps = { 45 | handler: RouteHandler 46 | }; 47 | 48 | module.exports = NotFoundRoute; -------------------------------------------------------------------------------- /build/npm/lib/components/Redirect.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 4 | 5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 6 | 7 | var PropTypes = require("../PropTypes"); 8 | var Route = require("./Route"); 9 | 10 | /** 11 | * A component is a special kind of that always 12 | * redirects to another route when it matches. 13 | */ 14 | 15 | var Redirect = (function (_Route) { 16 | function Redirect() { 17 | _classCallCheck(this, Redirect); 18 | 19 | if (_Route != null) { 20 | _Route.apply(this, arguments); 21 | } 22 | } 23 | 24 | _inherits(Redirect, _Route); 25 | 26 | return Redirect; 27 | })(Route); 28 | 29 | // TODO: Include these in the above class definition 30 | // once we can use ES7 property initializers. 31 | // https://github.com/babel/babel/issues/619 32 | 33 | Redirect.propTypes = { 34 | path: PropTypes.string, 35 | from: PropTypes.string, // Alias for path. 36 | to: PropTypes.string, 37 | handler: PropTypes.falsy 38 | }; 39 | 40 | // Redirects should not have a default handler 41 | Redirect.defaultProps = {}; 42 | 43 | module.exports = Redirect; -------------------------------------------------------------------------------- /build/npm/lib/components/Route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 6 | 7 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 8 | 9 | var React = require("react"); 10 | var invariant = require("react/lib/invariant"); 11 | var PropTypes = require("../PropTypes"); 12 | var RouteHandler = require("./RouteHandler"); 13 | 14 | /** 15 | * components specify components that are rendered to the page when the 16 | * URL matches a given pattern. 17 | * 18 | * Routes are arranged in a nested tree structure. When a new URL is requested, 19 | * the tree is searched depth-first to find a route whose path matches the URL. 20 | * When one is found, all routes in the tree that lead to it are considered 21 | * "active" and their components are rendered into the DOM, nested in the same 22 | * order as they are in the tree. 23 | * 24 | * The preferred way to configure a router is using JSX. The XML-like syntax is 25 | * a great way to visualize how routes are laid out in an application. 26 | * 27 | * var routes = [ 28 | * 29 | * 30 | * 31 | * 32 | * 33 | * ]; 34 | * 35 | * Router.run(routes, function (Handler) { 36 | * React.render(, document.body); 37 | * }); 38 | * 39 | * Handlers for Route components that contain children can render their active 40 | * child route using a element. 41 | * 42 | * var App = React.createClass({ 43 | * render: function () { 44 | * return ( 45 | *
46 | * 47 | *
48 | * ); 49 | * } 50 | * }); 51 | * 52 | * If no handler is provided for the route, it will render a matched child route. 53 | */ 54 | 55 | var Route = (function (_React$Component) { 56 | function Route() { 57 | _classCallCheck(this, Route); 58 | 59 | if (_React$Component != null) { 60 | _React$Component.apply(this, arguments); 61 | } 62 | } 63 | 64 | _inherits(Route, _React$Component); 65 | 66 | _createClass(Route, { 67 | render: { 68 | value: function render() { 69 | invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.name); 70 | } 71 | } 72 | }); 73 | 74 | return Route; 75 | })(React.Component); 76 | 77 | // TODO: Include these in the above class definition 78 | // once we can use ES7 property initializers. 79 | // https://github.com/babel/babel/issues/619 80 | 81 | Route.propTypes = { 82 | name: PropTypes.string, 83 | path: PropTypes.string, 84 | handler: PropTypes.func, 85 | ignoreScrollBehavior: PropTypes.bool 86 | }; 87 | 88 | Route.defaultProps = { 89 | handler: RouteHandler 90 | }; 91 | 92 | module.exports = Route; -------------------------------------------------------------------------------- /build/npm/lib/components/RouteHandler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 4 | 5 | var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; 6 | 7 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 8 | 9 | var React = require("react"); 10 | var ContextWrapper = require("./ContextWrapper"); 11 | var assign = require("react/lib/Object.assign"); 12 | var PropTypes = require("../PropTypes"); 13 | 14 | var REF_NAME = "__routeHandler__"; 15 | 16 | /** 17 | * A component renders the active child route handler 18 | * when routes are nested. 19 | */ 20 | 21 | var RouteHandler = (function (_React$Component) { 22 | function RouteHandler() { 23 | _classCallCheck(this, RouteHandler); 24 | 25 | if (_React$Component != null) { 26 | _React$Component.apply(this, arguments); 27 | } 28 | } 29 | 30 | _inherits(RouteHandler, _React$Component); 31 | 32 | _createClass(RouteHandler, { 33 | getChildContext: { 34 | value: function getChildContext() { 35 | return { 36 | routeDepth: this.context.routeDepth + 1 37 | }; 38 | } 39 | }, 40 | componentDidMount: { 41 | value: function componentDidMount() { 42 | this._updateRouteComponent(this.refs[REF_NAME]); 43 | } 44 | }, 45 | componentDidUpdate: { 46 | value: function componentDidUpdate() { 47 | this._updateRouteComponent(this.refs[REF_NAME]); 48 | } 49 | }, 50 | componentWillUnmount: { 51 | value: function componentWillUnmount() { 52 | this._updateRouteComponent(null); 53 | } 54 | }, 55 | _updateRouteComponent: { 56 | value: function _updateRouteComponent(component) { 57 | this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); 58 | } 59 | }, 60 | getRouteDepth: { 61 | value: function getRouteDepth() { 62 | return this.context.routeDepth; 63 | } 64 | }, 65 | createChildRouteHandler: { 66 | value: function createChildRouteHandler(props) { 67 | var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); 68 | return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; 69 | } 70 | }, 71 | render: { 72 | value: function render() { 73 | var handler = this.createChildRouteHandler(); 74 | // 9 | 10 | -------------------------------------------------------------------------------- /examples/async-data/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Arial; 3 | font-weight: 200; 4 | } 5 | 6 | a { 7 | color: hsl(200, 50%, 50%); 8 | } 9 | 10 | a.active { 11 | color: hsl(20, 50%, 50%); 12 | } 13 | 14 | .loading { 15 | opacity: 0.5; 16 | } 17 | 18 | #example { 19 | position: absolute; 20 | } 21 | 22 | .App { 23 | position: absolute; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | bottom: 0; 28 | width: 500px; 29 | height: 500px; 30 | } 31 | 32 | .ContactList { 33 | position: absolute; 34 | left: 0; 35 | top: 0; 36 | bottom: 0; 37 | width: 300px; 38 | overflow: auto; 39 | padding: 20px; 40 | } 41 | 42 | .Content { 43 | position: absolute; 44 | left: 300px; 45 | top: 0; 46 | bottom: 0; 47 | right: 0; 48 | border-left: 1px solid #ccc; 49 | overflow: auto; 50 | padding: 40px; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /examples/async-data/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var EventEmitter = require('events').EventEmitter; 4 | var { Route, DefaultRoute, RouteHandler, Link } = Router; 5 | 6 | var API = 'http://addressbook-api.herokuapp.com'; 7 | var loadingEvents = new EventEmitter(); 8 | 9 | function getJSON(url) { 10 | if (getJSON._cache[url]) 11 | return Promise.resolve(getJSON._cache[url]); 12 | 13 | return new Promise((resolve, reject) => { 14 | var req = new XMLHttpRequest(); 15 | req.onload = function () { 16 | if (req.status === 404) { 17 | reject(new Error('not found')); 18 | } else { 19 | // fake a slow response every now and then 20 | setTimeout(function () { 21 | var data = JSON.parse(req.response); 22 | resolve(data); 23 | getJSON._cache[url] = data; 24 | }, Math.random() > 0.5 ? 0 : 1000); 25 | } 26 | }; 27 | req.open('GET', url); 28 | req.send(); 29 | }); 30 | } 31 | getJSON._cache = {}; 32 | 33 | var App = React.createClass({ 34 | 35 | statics: { 36 | fetchData (params) { 37 | return getJSON(`${API}/contacts`).then((res) => res.contacts); 38 | } 39 | }, 40 | 41 | getInitialState () { 42 | return { loading: false }; 43 | }, 44 | 45 | componentDidMount () { 46 | var timer; 47 | loadingEvents.on('loadStart', () => { 48 | clearTimeout(timer); 49 | // for slow responses, indicate the app is thinking 50 | // otherwise its fast enough to just wait for the 51 | // data to load 52 | timer = setTimeout(() => { 53 | this.setState({ loading: true }); 54 | }, 300); 55 | }); 56 | 57 | loadingEvents.on('loadEnd', () => { 58 | clearTimeout(timer); 59 | this.setState({ loading: false }); 60 | }); 61 | }, 62 | 63 | renderContacts () { 64 | return this.props.data.contacts.map((contact, i) => { 65 | return ( 66 |
  • 67 | {contact.first} {contact.last} 68 |
  • 69 | ); 70 | }); 71 | }, 72 | 73 | render () { 74 | return ( 75 |
    76 |
      77 | {this.renderContacts()} 78 |
    79 | 80 |
    81 | ); 82 | } 83 | }); 84 | 85 | var Contact = React.createClass({ 86 | statics: { 87 | fetchData (params) { 88 | return getJSON(`${API}/contacts/${params.id}`).then((res) => res.contact); 89 | } 90 | }, 91 | 92 | render () { 93 | var { contact } = this.props.data; 94 | return ( 95 |
    96 |

    Back

    97 |

    {contact.first} {contact.last}

    98 | 99 |
    100 | ); 101 | } 102 | }); 103 | 104 | var Index = React.createClass({ 105 | render () { 106 | return ( 107 |
    108 |

    Welcome!

    109 |
    110 | ); 111 | } 112 | }); 113 | 114 | var routes = ( 115 | 116 | 117 | 118 | 119 | ); 120 | 121 | function fetchData(routes, params) { 122 | var data = {}; 123 | return Promise.all(routes 124 | .filter(route => route.handler.fetchData) 125 | .map(route => { 126 | return route.handler.fetchData(params).then(d => {data[route.name] = d;}); 127 | }) 128 | ).then(() => data); 129 | } 130 | 131 | Router.run(routes, function (Handler, state) { 132 | loadingEvents.emit('loadStart'); 133 | 134 | fetchData(state.routes, state.params).then((data) => { 135 | loadingEvents.emit('loadEnd'); 136 | React.render(, document.getElementById('example')); 137 | }); 138 | }); 139 | -------------------------------------------------------------------------------- /examples/async-data/index.html: -------------------------------------------------------------------------------- 1 | 2 | Master Detail Example 3 | 4 | 5 | 6 |

    React Router Examples / Async Data

    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/auth-flow/index.html: -------------------------------------------------------------------------------- 1 | 2 | Authentication Flow Example 3 | 4 | 5 |

    React Router Examples / Auth Flow

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/data-flow/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Arial; 3 | font-weight: 200; 4 | } 5 | 6 | a { 7 | color: hsl(200, 50%, 50%); 8 | } 9 | 10 | a.active { 11 | color: hsl(20, 50%, 50%); 12 | } 13 | 14 | #example { 15 | position: absolute; 16 | } 17 | 18 | .App { 19 | position: absolute; 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | bottom: 0; 24 | width: 500px; 25 | height: 500px; 26 | } 27 | 28 | .Master { 29 | position: absolute; 30 | left: 0; 31 | top: 0; 32 | bottom: 0; 33 | width: 300px; 34 | overflow: auto; 35 | padding: 10px 40px; 36 | } 37 | 38 | .Detail { 39 | position: absolute; 40 | left: 300px; 41 | top: 0; 42 | bottom: 0; 43 | right: 0; 44 | border-left: 1px solid #ccc; 45 | overflow: auto; 46 | padding: 40px; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/data-flow/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | 7 | contextTypes: { 8 | router: React.PropTypes.func 9 | }, 10 | 11 | getInitialState: function () { 12 | return { 13 | tacos: [ 14 | { name: 'duck confit' }, 15 | { name: 'carne asada' }, 16 | { name: 'shrimp' } 17 | ] 18 | }; 19 | }, 20 | 21 | addTaco: function () { 22 | var name = prompt('taco name?'); 23 | this.setState({ 24 | tacos: this.state.tacos.concat({name: name}) 25 | }); 26 | }, 27 | 28 | handleRemoveTaco: function (removedTaco) { 29 | var tacos = this.state.tacos.filter(function (taco) { 30 | return taco.name != removedTaco; 31 | }); 32 | this.setState({tacos: tacos}); 33 | this.context.router.transitionTo('/'); 34 | }, 35 | 36 | render: function () { 37 | var links = this.state.tacos.map(function (taco, i) { 38 | return ( 39 |
  • 40 | {taco.name} 41 |
  • 42 | ); 43 | }); 44 | return ( 45 |
    46 | 47 |
      48 | {links} 49 |
    50 |
    51 | 52 |
    53 |
    54 | ); 55 | } 56 | }); 57 | 58 | var Taco = React.createClass({ 59 | 60 | contextTypes: { 61 | router: React.PropTypes.func 62 | }, 63 | 64 | remove: function () { 65 | this.props.onRemoveTaco(this.context.router.getCurrentParams().name); 66 | }, 67 | 68 | render: function () { 69 | return ( 70 |
    71 |

    {this.context.router.getCurrentParams().name}

    72 | 73 |
    74 | ); 75 | } 76 | }); 77 | 78 | var routes = ( 79 | 80 | 81 | 82 | ); 83 | 84 | Router.run(routes, function (Handler) { 85 | React.render(, document.getElementById('example')); 86 | }); 87 | -------------------------------------------------------------------------------- /examples/data-flow/index.html: -------------------------------------------------------------------------------- 1 | 2 | Data Flow Example 3 | 4 | 5 | 6 |

    React Router Examples / Data Flow

    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/dynamic-segments/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, Redirect, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | render () { 7 | return ( 8 |
    9 |
      10 |
    • Bob
    • 11 |
    • Sally
    • 12 |
    13 | 14 |
    15 | ); 16 | } 17 | }); 18 | 19 | var User = React.createClass({ 20 | 21 | contextTypes: { 22 | router: React.PropTypes.func 23 | }, 24 | 25 | render () { 26 | var { userId } = this.context.router.getCurrentParams(); 27 | return ( 28 |
    29 |

    User id: {userId}

    30 |
      31 |
    • foo task
    • 32 |
    • bar task
    • 33 |
    34 | 35 |
    36 | ); 37 | } 38 | }); 39 | 40 | 41 | var Task = React.createClass({ 42 | 43 | contextTypes: { 44 | router: React.PropTypes.func 45 | }, 46 | 47 | render () { 48 | var { userId, taskId } = this.context.router.getCurrentParams(); 49 | return ( 50 |
    51 |

    User id: {userId}

    52 |

    Task id: {taskId}

    53 |
    54 | ); 55 | } 56 | }); 57 | 58 | var routes = ( 59 | 60 | 61 | 62 | 63 | 64 | 65 | ); 66 | 67 | Router.run(routes, function (Handler) { 68 | React.render(, document.getElementById('example')); 69 | }); 70 | -------------------------------------------------------------------------------- /examples/dynamic-segments/index.html: -------------------------------------------------------------------------------- 1 | 2 | Dynamic Segments Example 3 | 4 | 5 |

    React Router Examples / Dynamic Segments

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Arial; 3 | font-weight: 200; 4 | } 5 | 6 | h1, h2, h3 { 7 | font-weight: 100; 8 | } 9 | 10 | a { 11 | color: hsl(200, 50%, 50%); 12 | } 13 | 14 | a.active { 15 | color: hsl(20, 50%, 50%); 16 | } 17 | 18 | .breadcrumbs a { 19 | text-decoration: none; 20 | } 21 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | React Router Examples 3 | 4 | 5 |

    React Router Examples

    6 | 21 | -------------------------------------------------------------------------------- /examples/master-detail/ContactStore.js: -------------------------------------------------------------------------------- 1 | var API = 'http://addressbook-api.herokuapp.com/contacts'; 2 | var _contacts = {}; 3 | var _changeListeners = []; 4 | var _initCalled = false; 5 | 6 | var ContactStore = module.exports = { 7 | 8 | init: function () { 9 | if (_initCalled) 10 | return; 11 | 12 | _initCalled = true; 13 | 14 | getJSON(API, function (err, res) { 15 | res.contacts.forEach(function (contact) { 16 | _contacts[contact.id] = contact; 17 | }); 18 | 19 | ContactStore.notifyChange(); 20 | }); 21 | }, 22 | 23 | addContact: function (contact, cb) { 24 | postJSON(API, { contact: contact }, function (res) { 25 | _contacts[res.contact.id] = res.contact; 26 | ContactStore.notifyChange(); 27 | if (cb) cb(res.contact); 28 | }); 29 | }, 30 | 31 | removeContact: function (id, cb) { 32 | deleteJSON(API + '/' + id, cb); 33 | delete _contacts[id]; 34 | ContactStore.notifyChange(); 35 | }, 36 | 37 | getContacts: function () { 38 | var array = []; 39 | 40 | for (var id in _contacts) 41 | array.push(_contacts[id]); 42 | 43 | return array; 44 | }, 45 | 46 | getContact: function (id) { 47 | return _contacts[id]; 48 | }, 49 | 50 | notifyChange: function () { 51 | _changeListeners.forEach(function (listener) { 52 | listener(); 53 | }); 54 | }, 55 | 56 | addChangeListener: function (listener) { 57 | _changeListeners.push(listener); 58 | }, 59 | 60 | removeChangeListener: function (listener) { 61 | _changeListeners = _changeListeners.filter(function (l) { 62 | return listener !== l; 63 | }); 64 | } 65 | 66 | }; 67 | 68 | function getJSON(url, cb) { 69 | var req = new XMLHttpRequest(); 70 | req.onload = function () { 71 | if (req.status === 404) { 72 | cb(new Error('not found')); 73 | } else { 74 | cb(null, JSON.parse(req.response)); 75 | } 76 | }; 77 | req.open('GET', url); 78 | req.send(); 79 | } 80 | 81 | function postJSON(url, obj, cb) { 82 | var req = new XMLHttpRequest(); 83 | req.onload = function () { 84 | cb(JSON.parse(req.response)); 85 | }; 86 | req.open('POST', url); 87 | req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); 88 | req.send(JSON.stringify(obj)); 89 | } 90 | 91 | function deleteJSON(url, cb) { 92 | var req = new XMLHttpRequest(); 93 | req.onload = cb; 94 | req.open('DELETE', url); 95 | req.send(); 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /examples/master-detail/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Arial; 3 | font-weight: 200; 4 | } 5 | 6 | a { 7 | color: hsl(200, 50%, 50%); 8 | } 9 | 10 | a.active { 11 | color: hsl(20, 50%, 50%); 12 | } 13 | 14 | #example { 15 | position: absolute; 16 | } 17 | 18 | .App { 19 | position: absolute; 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | bottom: 0; 24 | width: 500px; 25 | height: 500px; 26 | } 27 | 28 | .ContactList { 29 | position: absolute; 30 | left: 0; 31 | top: 0; 32 | bottom: 0; 33 | width: 300px; 34 | overflow: auto; 35 | padding: 20px; 36 | } 37 | 38 | .Content { 39 | position: absolute; 40 | left: 300px; 41 | top: 0; 42 | bottom: 0; 43 | right: 0; 44 | border-left: 1px solid #ccc; 45 | overflow: auto; 46 | padding: 40px; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/master-detail/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var ContactStore = require('./ContactStore'); 4 | var { 5 | Route, 6 | DefaultRoute, 7 | NotFoundRoute, 8 | RouteHandler, 9 | Link 10 | } = Router; 11 | 12 | var App = React.createClass({ 13 | getInitialState: function () { 14 | return { 15 | contacts: ContactStore.getContacts(), 16 | loading: true 17 | }; 18 | }, 19 | 20 | componentWillMount: function () { 21 | ContactStore.init(); 22 | }, 23 | 24 | componentDidMount: function () { 25 | ContactStore.addChangeListener(this.updateContacts); 26 | }, 27 | 28 | componentWillUnmount: function () { 29 | ContactStore.removeChangeListener(this.updateContacts); 30 | }, 31 | 32 | updateContacts: function () { 33 | if (!this.isMounted()) 34 | return; 35 | 36 | this.setState({ 37 | contacts: ContactStore.getContacts(), 38 | loading: false 39 | }); 40 | }, 41 | 42 | render: function () { 43 | var contacts = this.state.contacts.map(function (contact) { 44 | return
  • {contact.first}
  • ; 45 | }); 46 | return ( 47 |
    48 |
    49 | New Contact 50 |
      51 | {contacts} 52 |
    53 | Invalid Link (not found) 54 |
    55 |
    56 | 57 |
    58 |
    59 | ); 60 | } 61 | }); 62 | 63 | var Index = React.createClass({ 64 | render: function () { 65 | return

    Address Book

    ; 66 | } 67 | }); 68 | 69 | var Contact = React.createClass({ 70 | 71 | contextTypes: { 72 | router: React.PropTypes.func 73 | }, 74 | 75 | getStateFromStore: function () { 76 | var id = this.context.router.getCurrentParams().id; 77 | return { 78 | contact: ContactStore.getContact(id) 79 | }; 80 | }, 81 | 82 | getInitialState: function () { 83 | return this.getStateFromStore(); 84 | }, 85 | 86 | componentDidMount: function () { 87 | ContactStore.addChangeListener(this.updateContact); 88 | }, 89 | 90 | componentWillUnmount: function () { 91 | ContactStore.removeChangeListener(this.updateContact); 92 | }, 93 | 94 | componentWillReceiveProps: function () { 95 | this.setState(this.getStateFromStore()); 96 | }, 97 | 98 | updateContact: function () { 99 | if (!this.isMounted()) 100 | return; 101 | 102 | this.setState(this.getStateFromStore()); 103 | }, 104 | 105 | destroy: function () { 106 | var { router } = this.context; 107 | var id = router.getCurrentParams().id; 108 | ContactStore.removeContact(id); 109 | router.transitionTo('/'); 110 | }, 111 | 112 | render: function () { 113 | var contact = this.state.contact || {}; 114 | var name = contact.first + ' ' + contact.last; 115 | var avatar = contact.avatar || 'http://placecage.com/50/50'; 116 | return ( 117 |
    118 | 119 |

    {name}

    120 | 121 |
    122 | ); 123 | } 124 | }); 125 | 126 | var NewContact = React.createClass({ 127 | 128 | contextTypes: { 129 | router: React.PropTypes.func 130 | }, 131 | 132 | createContact: function (event) { 133 | event.preventDefault(); 134 | ContactStore.addContact({ 135 | first: this.refs.first.getDOMNode().value, 136 | last: this.refs.last.getDOMNode().value 137 | }, function (contact) { 138 | this.context.router.transitionTo('contact', { id: contact.id }); 139 | }.bind(this)); 140 | }, 141 | 142 | render: function () { 143 | return ( 144 |
    145 |

    146 | 147 | 148 |

    149 |

    150 | Cancel 151 |

    152 |
    153 | ); 154 | } 155 | }); 156 | 157 | var NotFound = React.createClass({ 158 | render: function () { 159 | return

    Not found

    ; 160 | } 161 | }); 162 | 163 | var routes = ( 164 | 165 | 166 | 167 | 168 | 169 | 170 | ); 171 | 172 | Router.run(routes, function (Handler) { 173 | React.render(, document.getElementById('example')); 174 | }); 175 | -------------------------------------------------------------------------------- /examples/master-detail/index.html: -------------------------------------------------------------------------------- 1 | 2 | Master Detail Example 3 | 4 | 5 | 6 |

    React Router Examples / Master Detail

    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/partial-app-loading/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, RouteHandler, Link } = Router; 4 | 5 | var AsyncElement = { 6 | loadedComponent: null, 7 | 8 | load: function () { 9 | if (this.constructor.loadedComponent) 10 | return; 11 | 12 | this.bundle(function (component) { 13 | this.constructor.loadedComponent = component; 14 | this.forceUpdate(); 15 | }.bind(this)); 16 | }, 17 | 18 | componentDidMount: function () { 19 | setTimeout(this.load, 1000); // feel it good 20 | }, 21 | 22 | render: function () { 23 | var Component = this.constructor.loadedComponent; 24 | if (Component) { 25 | // can't find RouteHandler in the loaded component, so we just grab 26 | // it here first. 27 | this.props.activeRoute = ; 28 | return ; 29 | } 30 | return this.preRender(); 31 | } 32 | }; 33 | 34 | var PreDashboard = React.createClass({ 35 | mixins: [ AsyncElement ], 36 | bundle: require('bundle?lazy!./dashboard.js'), 37 | preRender: function () { 38 | return
    Loading dashboard...
    ; 39 | } 40 | }); 41 | 42 | var PreInbox = React.createClass({ 43 | mixins: [ AsyncElement ], 44 | bundle: require('bundle?lazy!./inbox.js'), 45 | preRender: function () { 46 | return
    Loading inbox...
    ; 47 | } 48 | }); 49 | 50 | var App = React.createClass({ 51 | render: function () { 52 | return ( 53 |
    54 |

    Partial App

    55 |
      56 |
    • Dashboard
    • 57 |
    58 | 59 |
    60 | ); 61 | } 62 | }); 63 | 64 | var routes = ( 65 | 66 | 67 | 68 | 69 | 70 | ); 71 | 72 | Router.run(routes, function (Handler) { 73 | React.render(, document.getElementById('example')); 74 | }); 75 | -------------------------------------------------------------------------------- /examples/partial-app-loading/dashboard.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { RouteHandler, Link } = Router; 4 | 5 | var Dashboard = React.createClass({ 6 | 7 | render: function () { 8 | return ( 9 |
    10 |

    Dashboard!

    11 |
      12 |
    • Inbox
    • 13 |
    14 | 15 |
    16 | ); 17 | } 18 | }); 19 | 20 | module.exports = Dashboard; 21 | -------------------------------------------------------------------------------- /examples/partial-app-loading/inbox.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var Inbox = React.createClass({ 4 | 5 | render: function () { 6 | return ( 7 |
    8 |

    Inbox!

    9 |
    10 | ); 11 | } 12 | }); 13 | 14 | module.exports = Inbox; 15 | -------------------------------------------------------------------------------- /examples/partial-app-loading/index.html: -------------------------------------------------------------------------------- 1 | 2 | Partial App Loading Example 3 | 4 | 5 |

    React Router Examples / Partial App Loading

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/query-params/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | render: function () { 7 | return ( 8 |
    9 |
      10 |
    • Bob
    • 11 |
    • Bob With Query Params
    • 12 |
    • Sally
    • 13 |
    14 | 15 |
    16 | ); 17 | } 18 | }); 19 | 20 | var User = React.createClass({ 21 | 22 | contextTypes: { 23 | router: React.PropTypes.func 24 | }, 25 | 26 | render: function () { 27 | var { router } = this.context; 28 | var age = router.getCurrentQuery().showAge ? '33' : ''; 29 | var userID = router.getCurrentParams().userID; 30 | return ( 31 |
    32 |

    User id: {userID}

    33 | {age} 34 |
    35 | ); 36 | } 37 | }); 38 | 39 | var routes = ( 40 | 41 | 42 | 43 | ); 44 | 45 | Router.run(routes, function (Handler) { 46 | React.render(, document.getElementById('example')); 47 | }); 48 | -------------------------------------------------------------------------------- /examples/query-params/index.html: -------------------------------------------------------------------------------- 1 | 2 | Query Params Example 3 | 4 | 5 |

    React Router Examples / Query Params

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/rx/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, RouteHandler, Link } = Router; 4 | var Rx = require('rx'); 5 | 6 | var App = React.createClass({ 7 | render () { 8 | return ( 9 |
    10 |
      11 |
    • Bob
    • 12 |
    • Sally
    • 13 |
    14 | 15 |
    16 | ); 17 | } 18 | }); 19 | 20 | var User = React.createClass({ 21 | 22 | contextTypes: { 23 | router: React.PropTypes.func 24 | }, 25 | 26 | render () { 27 | var { userId } = this.context.router.getCurrentParams(); 28 | return ( 29 |
    30 |

    User id: {userId}

    31 |
    32 | ); 33 | } 34 | }); 35 | 36 | var routes = ( 37 | 38 | 39 | 40 | ); 41 | 42 | var source = Rx.Observable.fromEventPattern(function(h) { 43 | Router.run(routes, h); 44 | }); 45 | 46 | source.subscribe(function (Handler) { 47 | React.render(, document.getElementById('example')); 48 | }); 49 | -------------------------------------------------------------------------------- /examples/rx/index.html: -------------------------------------------------------------------------------- 1 | 2 | Reactive Extenstions Example 3 | 4 | 5 |

    React Router Examples / Reactive Extensions

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/shared-root/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | render: function () { 7 | return ( 8 |
    9 |
      10 |
    1. Home
    2. 11 |
    3. Sign in
    4. 12 |
    5. Forgot Password
    6. 13 |
    14 | 15 |
    16 | ); 17 | } 18 | }); 19 | 20 | var SignedIn = React.createClass({ 21 | render: function () { 22 | return ( 23 |
    24 |

    Signed In

    25 | 26 |
    27 | ); 28 | } 29 | }); 30 | 31 | var Home = React.createClass({ 32 | render: function () { 33 | return ( 34 |

    Welcome home!

    35 | ); 36 | } 37 | }); 38 | 39 | var SignedOut = React.createClass({ 40 | render: function () { 41 | return ( 42 |
    43 |

    Signed Out

    44 | 45 |
    46 | ); 47 | } 48 | }); 49 | 50 | var SignIn = React.createClass({ 51 | render: function () { 52 | return ( 53 |

    Please sign in.

    54 | ); 55 | } 56 | }); 57 | 58 | var ForgotPassword = React.createClass({ 59 | render: function () { 60 | return ( 61 |

    Forgot your password?

    62 | ); 63 | } 64 | }); 65 | 66 | var routes = ( 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ); 77 | 78 | Router.run(routes, function (Handler) { 79 | React.render(, document.getElementById('example')); 80 | }); 81 | -------------------------------------------------------------------------------- /examples/shared-root/index.html: -------------------------------------------------------------------------------- 1 | 2 | Shared Root Example 3 | 4 | 5 |

    React Router Examples / Shared Root

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/sidebar/app.css: -------------------------------------------------------------------------------- 1 | .Sidebar { 2 | float: left; 3 | background: #eee; 4 | padding: 20px; 5 | margin: 0 20px 20px 20px; 6 | width: 200px; 7 | cursor: pointer; 8 | } 9 | 10 | .Content { 11 | padding: 20px 20px 20px 300px; 12 | } 13 | 14 | .CategoryNav__Toggle:before { 15 | display: inline-block; 16 | width: 1em; 17 | content: '▸'; 18 | } 19 | 20 | .CategoryNav__Toggle--is-open:before { 21 | content: '▾'; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /examples/sidebar/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, DefaultRoute, RouteHandler, Link } = Router; 4 | var data = require('./data'); 5 | 6 | var CategoryNav = React.createClass({ 7 | getInitialState: function () { 8 | return { isOpen: this.props.defaultIsOpen}; 9 | }, 10 | 11 | getDefaultProps: function () { 12 | return { isOpen: false }; 13 | }, 14 | 15 | componentWillReceiveProps: function (newProps) { 16 | if (!this.state.isOpen) 17 | this.setState({ isOpen: newProps.defaultIsOpen }); 18 | }, 19 | 20 | toggle: function () { 21 | this.setState({ isOpen: !this.state.isOpen }); 22 | }, 23 | 24 | buildToggleClassName: function () { 25 | var toggleClassName = 'CategoryNav__Toggle'; 26 | if (this.state.isOpen) 27 | toggleClassName += ' CategoryNav__Toggle--is-open'; 28 | return toggleClassName; 29 | }, 30 | 31 | renderItems: function () { 32 | var category = this.props.category; 33 | return this.state.isOpen ? category.items.map(function (item) { 34 | var params = { name: item.name, category: category.name }; 35 | return ( 36 |
  • 37 | {item.name} 38 |
  • 39 | ); 40 | }) : null; 41 | }, 42 | 43 | render: function () { 44 | var category = this.props.category; 45 | return ( 46 |
    47 |

    {category.name}

    51 |
      {this.renderItems()}
    52 |
    53 | ); 54 | } 55 | }); 56 | 57 | var Sidebar = React.createClass({ 58 | renderCategory: function (category) { 59 | return ; 64 | }, 65 | 66 | render: function () { 67 | return ( 68 |
    69 | {this.props.categories.map(this.renderCategory)} 70 |
    71 | ); 72 | } 73 | }); 74 | 75 | var App = React.createClass({ 76 | 77 | contextTypes: { 78 | router: React.PropTypes.func.isRequired 79 | }, 80 | 81 | render: function () { 82 | var activeCategory = this.context.router.getCurrentParams().category; 83 | return ( 84 |
    85 | 86 |
    87 | 88 |
    89 |
    90 | ); 91 | } 92 | }); 93 | 94 | var Item = React.createClass({ 95 | 96 | contextTypes: { 97 | router: React.PropTypes.func.isRequired 98 | }, 99 | 100 | render: function () { 101 | var params = this.context.router.getCurrentParams(); 102 | var category = data.lookupCategory(params.category); 103 | var item = data.lookupItem(params.category, params.name); 104 | return ( 105 |
    106 |

    {category.name} / {item.name}

    107 |

    Price: ${item.price}

    108 |
    109 | ); 110 | } 111 | }); 112 | 113 | var Index = React.createClass({ 114 | render: function () { 115 | return ( 116 |
    117 |

    Sidebar features:

    118 |
      119 |
    • User can open/close categories
    • 120 |
    • 121 | Visiting an item on first page load will automatically open 122 | the correct category. (Click an item, then reload the 123 | browser.) 124 |
    • 125 |
    • 126 | Navigating with forward/back buttons will open an active 127 | category if it is not already open. (Navigate to several 128 | items, close all the categories, then use back/forward 129 | buttons.) 130 |
    • 131 |
    • 132 | Only the user can close a category. (Navigating from an 133 | active category will not close it.) 134 |
    • 135 |
    136 |
    137 | ); 138 | } 139 | }); 140 | 141 | var routes = ( 142 | 143 | 144 | 145 | 146 | ); 147 | 148 | Router.run(routes, function (Handler) { 149 | React.render(, document.getElementById('example')); 150 | }); 151 | -------------------------------------------------------------------------------- /examples/sidebar/data.js: -------------------------------------------------------------------------------- 1 | var data = [ 2 | { 3 | name: 'Tacos', 4 | items: [ 5 | { name: 'Carne Asada', price: 7 }, 6 | { name: 'Pollo', price: 6 }, 7 | { name: 'Carnitas', price: 6 } 8 | ] 9 | }, 10 | { 11 | name: 'Burgers', 12 | items: [ 13 | { name: 'Buffalo Bleu', price: 8 }, 14 | { name: 'Bacon', price: 8 }, 15 | { name: 'Mushroom and Swiss', price: 6 } 16 | ] 17 | }, 18 | { 19 | name: 'Drinks', 20 | items: [ 21 | { name: 'Lemonade', price: 3 }, 22 | { name: 'Root Beer', price: 4 }, 23 | { name: 'Iron Port', price: 5 } 24 | ] 25 | } 26 | ]; 27 | 28 | var dataMap = data.reduce(function (map, category) { 29 | category.itemsMap = category.items.reduce(function (itemsMap, item) { 30 | itemsMap[item.name] = item; 31 | return itemsMap; 32 | }, {}); 33 | map[category.name] = category; 34 | return map; 35 | }, {}); 36 | 37 | exports.getAll = function () { 38 | return data; 39 | }; 40 | 41 | exports.lookupCategory = function (name) { 42 | return dataMap[name]; 43 | }; 44 | 45 | exports.lookupItem = function (category, item) { 46 | return dataMap[category].itemsMap[item]; 47 | }; 48 | 49 | -------------------------------------------------------------------------------- /examples/sidebar/index.html: -------------------------------------------------------------------------------- 1 | 2 | Sidebar Example 3 | 4 | 5 | 6 |

    React Router Examples / Sidebar

    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/simple-master-detail/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Helvetica Neue", Arial; 3 | font-weight: 200; 4 | } 5 | 6 | a { 7 | color: hsl(200, 50%, 50%); 8 | } 9 | 10 | a.active { 11 | color: hsl(20, 50%, 50%); 12 | } 13 | 14 | #example { 15 | position: absolute; 16 | } 17 | 18 | .App { 19 | position: absolute; 20 | top: 0; 21 | left: 0; 22 | right: 0; 23 | bottom: 0; 24 | width: 1000px; 25 | height: 800px; 26 | } 27 | 28 | .Master { 29 | position: absolute; 30 | left: 0; 31 | top: 0; 32 | bottom: 0; 33 | width: 200px; 34 | overflow: auto; 35 | padding: 10px 40px; 36 | } 37 | 38 | .Detail { 39 | position: absolute; 40 | left: 300px; 41 | top: 0; 42 | bottom: 0; 43 | right: 0; 44 | border-left: 1px solid #ccc; 45 | overflow: auto; 46 | padding: 40px; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /examples/simple-master-detail/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, DefaultRoute, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | getInitialState: function () { 7 | return { states: findStates() }; 8 | }, 9 | 10 | render: function () { 11 | var links = this.state.states.map(function (state) { 12 | return ( 13 |
  • 14 | {state.name} 18 |
  • 19 | ); 20 | }); 21 | return ( 22 |
    23 |
      24 | {links} 25 |
    26 |
    27 | 28 |
    29 |
    30 | ); 31 | } 32 | }); 33 | 34 | var Index = React.createClass({ 35 | render: function () { 36 | return

    Select a state from the left

    ; 37 | } 38 | }); 39 | 40 | var State = React.createClass({ 41 | 42 | contextTypes: { 43 | router: React.PropTypes.func 44 | }, 45 | 46 | imageUrl: function (name) { 47 | return "http://www.50states.com/maps/" + underscore(name) + ".gif"; 48 | }, 49 | 50 | render: function () { 51 | var unitedState = findState(this.context.router.getCurrentParams().abbr); 52 | return ( 53 |
    54 |

    {unitedState.name}

    55 | 56 |
    57 | ); 58 | } 59 | }); 60 | 61 | var routes = ( 62 | 63 | 64 | 65 | 66 | ); 67 | 68 | Router.run(routes, function (Handler) { 69 | React.render(, document.getElementById('example')); 70 | }); 71 | 72 | /*****************************************************************************/ 73 | // data stuff 74 | 75 | function findState(abbr) { 76 | var states = findStates(); 77 | for (var i = 0, l = states.length; i < l; i ++) { 78 | if (states[i].abbr === abbr) { 79 | return states[i]; 80 | } 81 | } 82 | } 83 | 84 | function findStates() { 85 | return [ 86 | { abbr: "AL", name: "Alabama"}, 87 | { abbr: "AK", name: "Alaska"}, 88 | { abbr: "AZ", name: "Arizona"}, 89 | { abbr: "AR", name: "Arkansas"}, 90 | { abbr: "CA", name: "California"}, 91 | { abbr: "CO", name: "Colorado"}, 92 | { abbr: "CT", name: "Connecticut"}, 93 | { abbr: "DE", name: "Delaware"}, 94 | { abbr: "FL", name: "Florida"}, 95 | { abbr: "GA", name: "Georgia"}, 96 | { abbr: "HI", name: "Hawaii"}, 97 | { abbr: "ID", name: "Idaho"}, 98 | { abbr: "IL", name: "Illinois"}, 99 | { abbr: "IN", name: "Indiana"}, 100 | { abbr: "IA", name: "Iowa"}, 101 | { abbr: "KS", name: "Kansas"}, 102 | { abbr: "KY", name: "Kentucky"}, 103 | { abbr: "LA", name: "Louisiana"}, 104 | { abbr: "ME", name: "Maine"}, 105 | { abbr: "MD", name: "Maryland"}, 106 | { abbr: "MA", name: "Massachusetts"}, 107 | { abbr: "MI", name: "Michigan"}, 108 | { abbr: "MN", name: "Minnesota"}, 109 | { abbr: "MS", name: "Mississippi"}, 110 | { abbr: "MO", name: "Missouri"}, 111 | { abbr: "MT", name: "Montana"}, 112 | { abbr: "NE", name: "Nebraska"}, 113 | { abbr: "NV", name: "Nevada"}, 114 | { abbr: "NH", name: "New Hampshire"}, 115 | { abbr: "NJ", name: "New Jersey"}, 116 | { abbr: "NM", name: "New Mexico"}, 117 | { abbr: "NY", name: "New York"}, 118 | { abbr: "NC", name: "North Carolina"}, 119 | { abbr: "ND", name: "North Dakota"}, 120 | { abbr: "OH", name: "Ohio"}, 121 | { abbr: "OK", name: "Oklahoma"}, 122 | { abbr: "OR", name: "Oregon"}, 123 | { abbr: "PA", name: "Pennsylvania"}, 124 | { abbr: "RI", name: "Rhode Island"}, 125 | { abbr: "SC", name: "South Carolina"}, 126 | { abbr: "SD", name: "South Dakota"}, 127 | { abbr: "TN", name: "Tennessee"}, 128 | { abbr: "TX", name: "Texas"}, 129 | { abbr: "UT", name: "Utah"}, 130 | { abbr: "VT", name: "Vermont"}, 131 | { abbr: "VA", name: "Virginia"}, 132 | { abbr: "WA", name: "Washington"}, 133 | { abbr: "WV", name: "West Virginia"}, 134 | { abbr: "WI", name: "Wisconsin"}, 135 | { abbr: "WY", name: "Wyoming"} 136 | ]; 137 | } 138 | 139 | function underscore(str) { 140 | return str.toLowerCase().replace(/ /, '_'); 141 | } 142 | -------------------------------------------------------------------------------- /examples/simple-master-detail/index.html: -------------------------------------------------------------------------------- 1 | 2 | Simple Master Detail Example 3 | 4 | 5 | 6 |

    React Router Examples / Simple Master Detail

    7 |
    8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/transitions/app.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var Router = require('react-router'); 3 | var { Route, DefaultRoute, RouteHandler, Link } = Router; 4 | 5 | var App = React.createClass({ 6 | render: function () { 7 | return ( 8 |
    9 |
      10 |
    • Dashboard
    • 11 |
    • Form
    • 12 |
    13 | 14 |
    15 | ); 16 | } 17 | }); 18 | 19 | var Home = React.createClass({ 20 | render: function () { 21 | return

    Home

    ; 22 | } 23 | }); 24 | 25 | var Dashboard = React.createClass({ 26 | render: function () { 27 | return

    Dashboard

    ; 28 | } 29 | }); 30 | 31 | var Form = React.createClass({ 32 | 33 | contextTypes: { 34 | router: React.PropTypes.func 35 | }, 36 | 37 | statics: { 38 | willTransitionFrom: function (transition, element) { 39 | if (element.refs.userInput.getDOMNode().value !== '') { 40 | if (!confirm('You have unsaved information, are you sure you want to leave this page?')) { 41 | transition.abort(); 42 | } 43 | } 44 | } 45 | }, 46 | 47 | handleSubmit: function (event) { 48 | event.preventDefault(); 49 | this.refs.userInput.getDOMNode().value = ''; 50 | this.context.router.transitionTo('/'); 51 | }, 52 | 53 | render: function () { 54 | return ( 55 |
    56 |
    57 |

    Click the dashboard link with text in the input.

    58 | 59 | 60 |
    61 |
    62 | ); 63 | } 64 | }); 65 | 66 | var routes = ( 67 | 68 | 69 | 70 | 71 | 72 | ); 73 | 74 | Router.run(routes, function (Handler) { 75 | React.render(, document.getElementById('example')); 76 | }); 77 | -------------------------------------------------------------------------------- /examples/transitions/index.html: -------------------------------------------------------------------------------- 1 | 2 | Transitions Example 3 | 4 | 5 |

    React Router Examples / Transitions

    6 |
    7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | 5 | function isDirectory(dir) { 6 | return fs.lstatSync(dir).isDirectory(); 7 | } 8 | 9 | module.exports = { 10 | 11 | devtool: 'inline-source-map', 12 | 13 | entry: fs.readdirSync(__dirname).reduce(function (entries, dir) { 14 | var isDraft = dir.charAt(0) === '_'; 15 | 16 | if (!isDraft && isDirectory(path.join(__dirname, dir))) 17 | entries[dir] = path.join(__dirname, dir, 'app.js'); 18 | 19 | return entries; 20 | }, {}), 21 | 22 | output: { 23 | path: 'examples/__build__', 24 | filename: '[name].js', 25 | chunkFilename: '[id].chunk.js', 26 | publicPath: '/__build__/' 27 | }, 28 | 29 | module: { 30 | loaders: [ 31 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' } 32 | ] 33 | }, 34 | 35 | resolve: { 36 | alias: { 37 | 'react-router': '../../modules/index' 38 | } 39 | }, 40 | 41 | plugins: [ 42 | new webpack.optimize.CommonsChunkPlugin('shared.js'), 43 | new webpack.DefinePlugin({ 44 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 45 | }) 46 | ] 47 | 48 | }; 49 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | 6 | browsers: [ process.env.CONTINUOUS_INTEGRATION ? 'Firefox' : 'Chrome' ], 7 | 8 | singleRun: process.env.CONTINUOUS_INTEGRATION === 'true', 9 | 10 | frameworks: [ 'mocha' ], 11 | 12 | files: [ 13 | 'tests.webpack.js' 14 | ], 15 | 16 | preprocessors: { 17 | 'tests.webpack.js': [ 'webpack', 'sourcemap' ] 18 | }, 19 | 20 | reporters: [ 'dots' ], 21 | 22 | webpack: { 23 | devtool: 'inline-source-map', 24 | module: { 25 | loaders: [ 26 | { test: /\.js$/, loader: 'babel-loader' } 27 | ] 28 | }, 29 | plugins: [ 30 | new webpack.DefinePlugin({ 31 | 'process.env.NODE_ENV': JSON.stringify('test') 32 | }) 33 | ] 34 | }, 35 | 36 | webpackServer: { 37 | noInfo: true 38 | } 39 | 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /modules/Cancellation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a cancellation caused by navigating away 3 | * before the previous transition has fully resolved. 4 | */ 5 | function Cancellation() {} 6 | 7 | module.exports = Cancellation; 8 | -------------------------------------------------------------------------------- /modules/History.js: -------------------------------------------------------------------------------- 1 | var invariant = require('react/lib/invariant'); 2 | var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM; 3 | 4 | var History = { 5 | 6 | /** 7 | * The current number of entries in the history. 8 | * 9 | * Note: This property is read-only. 10 | */ 11 | length: 1, 12 | 13 | /** 14 | * Sends the browser back one entry in the history. 15 | */ 16 | back: function () { 17 | invariant( 18 | canUseDOM, 19 | 'Cannot use History.back without a DOM' 20 | ); 21 | 22 | // Do this first so that History.length will 23 | // be accurate in location change listeners. 24 | History.length -= 1; 25 | 26 | window.history.back(); 27 | } 28 | 29 | }; 30 | 31 | module.exports = History; 32 | -------------------------------------------------------------------------------- /modules/Match.js: -------------------------------------------------------------------------------- 1 | /* jshint -W084 */ 2 | var PathUtils = require('./PathUtils'); 3 | 4 | function deepSearch(route, pathname, query) { 5 | // Check the subtree first to find the most deeply-nested match. 6 | var childRoutes = route.childRoutes; 7 | if (childRoutes) { 8 | var match, childRoute; 9 | for (var i = 0, len = childRoutes.length; i < len; ++i) { 10 | childRoute = childRoutes[i]; 11 | 12 | if (childRoute.isDefault || childRoute.isNotFound) 13 | continue; // Check these in order later. 14 | 15 | if (match = deepSearch(childRoute, pathname, query)) { 16 | // A route in the subtree matched! Add this route and we're done. 17 | match.routes.unshift(route); 18 | return match; 19 | } 20 | } 21 | } 22 | 23 | // No child routes matched; try the default route. 24 | var defaultRoute = route.defaultRoute; 25 | if (defaultRoute && (params = PathUtils.extractParams(defaultRoute.path, pathname))) 26 | return new Match(pathname, params, query, [ route, defaultRoute ]); 27 | 28 | // Does the "not found" route match? 29 | var notFoundRoute = route.notFoundRoute; 30 | if (notFoundRoute && (params = PathUtils.extractParams(notFoundRoute.path, pathname))) 31 | return new Match(pathname, params, query, [ route, notFoundRoute ]); 32 | 33 | // Last attempt: check this route. 34 | var params = PathUtils.extractParams(route.path, pathname); 35 | if (params) 36 | return new Match(pathname, params, query, [ route ]); 37 | 38 | return null; 39 | } 40 | 41 | class Match { 42 | 43 | /** 44 | * Attempts to match depth-first a route in the given route's 45 | * subtree against the given path and returns the match if it 46 | * succeeds, null if no match can be made. 47 | */ 48 | static findMatch(routes, path) { 49 | var pathname = PathUtils.withoutQuery(path); 50 | var query = PathUtils.extractQuery(path); 51 | var match = null; 52 | 53 | for (var i = 0, len = routes.length; match == null && i < len; ++i) 54 | match = deepSearch(routes[i], pathname, query); 55 | 56 | return match; 57 | } 58 | 59 | constructor(pathname, params, query, routes) { 60 | this.pathname = pathname; 61 | this.params = params; 62 | this.query = query; 63 | this.routes = routes; 64 | } 65 | 66 | } 67 | 68 | module.exports = Match; 69 | -------------------------------------------------------------------------------- /modules/Navigation.js: -------------------------------------------------------------------------------- 1 | var warning = require('react/lib/warning'); 2 | var PropTypes = require('./PropTypes'); 3 | 4 | function deprecatedMethod(routerMethodName, fn) { 5 | return function () { 6 | warning( 7 | false, 8 | `Router.Navigation is deprecated. Please use this.context.router.${routerMethodName}() instead` 9 | ); 10 | 11 | return fn.apply(this, arguments); 12 | }; 13 | } 14 | 15 | /** 16 | * A mixin for components that modify the URL. 17 | * 18 | * Example: 19 | * 20 | * var MyLink = React.createClass({ 21 | * mixins: [ Router.Navigation ], 22 | * handleClick(event) { 23 | * event.preventDefault(); 24 | * this.transitionTo('aRoute', { the: 'params' }, { the: 'query' }); 25 | * }, 26 | * render() { 27 | * return ( 28 | * Click me! 29 | * ); 30 | * } 31 | * }); 32 | */ 33 | var Navigation = { 34 | 35 | contextTypes: { 36 | router: PropTypes.router.isRequired 37 | }, 38 | 39 | /** 40 | * Returns an absolute URL path created from the given route 41 | * name, URL parameters, and query values. 42 | */ 43 | makePath: deprecatedMethod('makePath', function (to, params, query) { 44 | return this.context.router.makePath(to, params, query); 45 | }), 46 | 47 | /** 48 | * Returns a string that may safely be used as the href of a 49 | * link to the route with the given name. 50 | */ 51 | makeHref: deprecatedMethod('makeHref', function (to, params, query) { 52 | return this.context.router.makeHref(to, params, query); 53 | }), 54 | 55 | /** 56 | * Transitions to the URL specified in the arguments by pushing 57 | * a new URL onto the history stack. 58 | */ 59 | transitionTo: deprecatedMethod('transitionTo', function (to, params, query) { 60 | this.context.router.transitionTo(to, params, query); 61 | }), 62 | 63 | /** 64 | * Transitions to the URL specified in the arguments by replacing 65 | * the current URL in the history stack. 66 | */ 67 | replaceWith: deprecatedMethod('replaceWith', function (to, params, query) { 68 | this.context.router.replaceWith(to, params, query); 69 | }), 70 | 71 | /** 72 | * Transitions to the previous URL. 73 | */ 74 | goBack: deprecatedMethod('goBack', function () { 75 | return this.context.router.goBack(); 76 | }) 77 | 78 | }; 79 | 80 | module.exports = Navigation; 81 | -------------------------------------------------------------------------------- /modules/PropTypes.js: -------------------------------------------------------------------------------- 1 | var assign = require('react/lib/Object.assign'); 2 | var ReactPropTypes = require('react').PropTypes; 3 | var Route = require('./Route'); 4 | 5 | var PropTypes = assign({}, ReactPropTypes, { 6 | 7 | /** 8 | * Indicates that a prop should be falsy. 9 | */ 10 | falsy(props, propName, componentName) { 11 | if (props[propName]) 12 | return new Error(`<${componentName}> may not have a "${propName}" prop`); 13 | }, 14 | 15 | /** 16 | * Indicates that a prop should be a Route object. 17 | */ 18 | route: ReactPropTypes.instanceOf(Route), 19 | 20 | /** 21 | * Indicates that a prop should be a Router object. 22 | */ 23 | //router: ReactPropTypes.instanceOf(Router) // TODO 24 | router: ReactPropTypes.func 25 | 26 | }); 27 | 28 | module.exports = PropTypes; 29 | -------------------------------------------------------------------------------- /modules/Redirect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Encapsulates a redirect to the given route. 3 | */ 4 | function Redirect(to, params, query) { 5 | this.to = to; 6 | this.params = params; 7 | this.query = query; 8 | } 9 | 10 | module.exports = Redirect; 11 | -------------------------------------------------------------------------------- /modules/ScrollHistory.js: -------------------------------------------------------------------------------- 1 | var invariant = require('react/lib/invariant'); 2 | var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM; 3 | var getWindowScrollPosition = require('./getWindowScrollPosition'); 4 | 5 | function shouldUpdateScroll(state, prevState) { 6 | if (!prevState) 7 | return true; 8 | 9 | // Don't update scroll position when only the query has changed. 10 | if (state.pathname === prevState.pathname) 11 | return false; 12 | 13 | var routes = state.routes; 14 | var prevRoutes = prevState.routes; 15 | 16 | var sharedAncestorRoutes = routes.filter(function (route) { 17 | return prevRoutes.indexOf(route) !== -1; 18 | }); 19 | 20 | return !sharedAncestorRoutes.some(function (route) { 21 | return route.ignoreScrollBehavior; 22 | }); 23 | } 24 | 25 | /** 26 | * Provides the router with the ability to manage window scroll position 27 | * according to its scroll behavior. 28 | */ 29 | var ScrollHistory = { 30 | 31 | statics: { 32 | 33 | /** 34 | * Records curent scroll position as the last known position for the given URL path. 35 | */ 36 | recordScrollPosition: function (path) { 37 | if (!this.scrollHistory) 38 | this.scrollHistory = {}; 39 | 40 | this.scrollHistory[path] = getWindowScrollPosition(); 41 | }, 42 | 43 | /** 44 | * Returns the last known scroll position for the given URL path. 45 | */ 46 | getScrollPosition: function (path) { 47 | if (!this.scrollHistory) 48 | this.scrollHistory = {}; 49 | 50 | return this.scrollHistory[path] || null; 51 | } 52 | 53 | }, 54 | 55 | componentWillMount: function () { 56 | invariant( 57 | this.constructor.getScrollBehavior() == null || canUseDOM, 58 | 'Cannot use scroll behavior without a DOM' 59 | ); 60 | }, 61 | 62 | componentDidMount: function () { 63 | this._updateScroll(); 64 | }, 65 | 66 | componentDidUpdate: function (prevProps, prevState) { 67 | this._updateScroll(prevState); 68 | }, 69 | 70 | _updateScroll: function (prevState) { 71 | if (!shouldUpdateScroll(this.state, prevState)) 72 | return; 73 | 74 | var scrollBehavior = this.constructor.getScrollBehavior(); 75 | 76 | if (scrollBehavior) 77 | scrollBehavior.updateScrollPosition( 78 | this.constructor.getScrollPosition(this.state.path), 79 | this.state.action 80 | ); 81 | } 82 | 83 | }; 84 | 85 | module.exports = ScrollHistory; 86 | -------------------------------------------------------------------------------- /modules/State.js: -------------------------------------------------------------------------------- 1 | var warning = require('react/lib/warning'); 2 | var PropTypes = require('./PropTypes'); 3 | 4 | function deprecatedMethod(routerMethodName, fn) { 5 | return function () { 6 | warning( 7 | false, 8 | `Router.State is deprecated. Please use this.context.router.${routerMethodName}() instead` 9 | ); 10 | 11 | return fn.apply(this, arguments); 12 | }; 13 | } 14 | 15 | /** 16 | * A mixin for components that need to know the path, routes, URL 17 | * params and query that are currently active. 18 | * 19 | * Example: 20 | * 21 | * var AboutLink = React.createClass({ 22 | * mixins: [ Router.State ], 23 | * render() { 24 | * var className = this.props.className; 25 | * 26 | * if (this.isActive('about')) 27 | * className += ' is-active'; 28 | * 29 | * return React.DOM.a({ className: className }, this.props.children); 30 | * } 31 | * }); 32 | */ 33 | var State = { 34 | 35 | contextTypes: { 36 | router: PropTypes.router.isRequired 37 | }, 38 | 39 | /** 40 | * Returns the current URL path. 41 | */ 42 | getPath: deprecatedMethod('getCurrentPath', function () { 43 | return this.context.router.getCurrentPath(); 44 | }), 45 | 46 | /** 47 | * Returns the current URL path without the query string. 48 | */ 49 | getPathname: deprecatedMethod('getCurrentPathname', function () { 50 | return this.context.router.getCurrentPathname(); 51 | }), 52 | 53 | /** 54 | * Returns an object of the URL params that are currently active. 55 | */ 56 | getParams: deprecatedMethod('getCurrentParams', function () { 57 | return this.context.router.getCurrentParams(); 58 | }), 59 | 60 | /** 61 | * Returns an object of the query params that are currently active. 62 | */ 63 | getQuery: deprecatedMethod('getCurrentQuery', function () { 64 | return this.context.router.getCurrentQuery(); 65 | }), 66 | 67 | /** 68 | * Returns an array of the routes that are currently active. 69 | */ 70 | getRoutes: deprecatedMethod('getCurrentRoutes', function () { 71 | return this.context.router.getCurrentRoutes(); 72 | }), 73 | 74 | /** 75 | * A helper method to determine if a given route, params, and query 76 | * are active. 77 | */ 78 | isActive: deprecatedMethod('isActive', function (to, params, query) { 79 | return this.context.router.isActive(to, params, query); 80 | }) 81 | 82 | }; 83 | 84 | module.exports = State; 85 | -------------------------------------------------------------------------------- /modules/TestUtils.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var RouteHandler = require('./components/RouteHandler'); 3 | var PropTypes = require('./PropTypes'); 4 | 5 | exports.Nested = React.createClass({ 6 | render: function () { 7 | return ( 8 |
    9 |

    Nested

    10 | 11 |
    12 | ); 13 | } 14 | }); 15 | 16 | exports.Foo = React.createClass({ 17 | render: function () { 18 | return
    Foo
    ; 19 | } 20 | }); 21 | 22 | exports.Bar = React.createClass({ 23 | render: function () { 24 | return
    Bar
    ; 25 | } 26 | }); 27 | 28 | exports.Baz = React.createClass({ 29 | render: function () { 30 | return
    Baz
    ; 31 | } 32 | }); 33 | 34 | exports.Async = React.createClass({ 35 | statics: { 36 | delay: 10, 37 | 38 | willTransitionTo: function (transition, params, query, callback) { 39 | setTimeout(callback, exports.Async.delay); 40 | } 41 | }, 42 | 43 | render: function () { 44 | return
    Async
    ; 45 | } 46 | }); 47 | 48 | exports.RedirectToFoo = React.createClass({ 49 | statics: { 50 | willTransitionTo: function (transition) { 51 | transition.redirect('/foo'); 52 | } 53 | }, 54 | 55 | render: function () { 56 | return null; 57 | } 58 | }); 59 | 60 | exports.RedirectToFooAsync = React.createClass({ 61 | statics: { 62 | delay: 10, 63 | 64 | willTransitionTo: function (transition, params, query, callback) { 65 | setTimeout(function () { 66 | transition.redirect('/foo'); 67 | callback(); 68 | }, exports.RedirectToFooAsync.delay); 69 | } 70 | }, 71 | 72 | render: function () { 73 | return null; 74 | } 75 | }); 76 | 77 | 78 | exports.Abort = React.createClass({ 79 | statics: { 80 | willTransitionTo: function (transition) { 81 | transition.abort(); 82 | } 83 | }, 84 | 85 | render: function () { 86 | return null; 87 | } 88 | }); 89 | 90 | exports.AbortAsync = React.createClass({ 91 | statics: { 92 | delay: 10, 93 | 94 | willTransitionTo: function (transition, params, query, callback) { 95 | setTimeout(function () { 96 | transition.abort(); 97 | callback(); 98 | }, exports.AbortAsync.delay); 99 | } 100 | }, 101 | 102 | render: function () { 103 | return null; 104 | } 105 | }); 106 | 107 | exports.EchoFooProp = React.createClass({ 108 | render: function () { 109 | return
    {this.props.foo}
    ; 110 | } 111 | }); 112 | 113 | exports.EchoBarParam = React.createClass({ 114 | contextTypes: { 115 | router: PropTypes.router.isRequired 116 | }, 117 | render: function () { 118 | return
    {this.context.router.getCurrentParams().bar}
    ; 119 | } 120 | }); 121 | -------------------------------------------------------------------------------- /modules/Transition.js: -------------------------------------------------------------------------------- 1 | /* jshint -W058 */ 2 | 3 | var Cancellation = require('./Cancellation'); 4 | var Redirect = require('./Redirect'); 5 | 6 | /** 7 | * Encapsulates a transition to a given path. 8 | * 9 | * The willTransitionTo and willTransitionFrom handlers receive 10 | * an instance of this class as their first argument. 11 | */ 12 | function Transition(path, retry) { 13 | this.path = path; 14 | this.abortReason = null; 15 | // TODO: Change this to router.retryTransition(transition) 16 | this.retry = retry.bind(this); 17 | } 18 | 19 | Transition.prototype.abort = function (reason) { 20 | if (this.abortReason == null) 21 | this.abortReason = reason || 'ABORT'; 22 | }; 23 | 24 | Transition.prototype.redirect = function (to, params, query) { 25 | this.abort(new Redirect(to, params, query)); 26 | }; 27 | 28 | Transition.prototype.cancel = function () { 29 | this.abort(new Cancellation); 30 | }; 31 | 32 | Transition.from = function (transition, routes, components, callback) { 33 | routes.reduce(function (callback, route, index) { 34 | return function (error) { 35 | if (error || transition.abortReason) { 36 | callback(error); 37 | } else if (route.onLeave) { 38 | try { 39 | route.onLeave(transition, components[index], callback); 40 | 41 | // If there is no callback in the argument list, call it automatically. 42 | if (route.onLeave.length < 3) 43 | callback(); 44 | } catch (e) { 45 | callback(e); 46 | } 47 | } else { 48 | callback(); 49 | } 50 | }; 51 | }, callback)(); 52 | }; 53 | 54 | Transition.to = function (transition, routes, params, query, callback) { 55 | routes.reduceRight(function (callback, route) { 56 | return function (error) { 57 | if (error || transition.abortReason) { 58 | callback(error); 59 | } else if (route.onEnter) { 60 | try { 61 | route.onEnter(transition, params, query, callback); 62 | 63 | // If there is no callback in the argument list, call it automatically. 64 | if (route.onEnter.length < 4) 65 | callback(); 66 | } catch (e) { 67 | callback(e); 68 | } 69 | } else { 70 | callback(); 71 | } 72 | }; 73 | }, callback)(); 74 | }; 75 | 76 | module.exports = Transition; 77 | -------------------------------------------------------------------------------- /modules/__tests__/History-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | var React = require('react'); 3 | var { Foo, RedirectToFoo } = require('../TestUtils'); 4 | var TestLocation = require('../locations/TestLocation'); 5 | var Route = require('../components/Route'); 6 | var History = require('../History'); 7 | var Router = require('../index'); 8 | 9 | describe('History', function () { 10 | describe('on the initial page load', function () { 11 | it('has length 1', function () { 12 | expect(History.length).toEqual(1); 13 | }); 14 | }); 15 | 16 | describe('after navigating to a route', function () { 17 | var location; 18 | beforeEach(function () { 19 | location = new TestLocation([ '/foo' ]); 20 | }); 21 | 22 | it('has length 2', function (done) { 23 | var routes = [ 24 | , 25 | 26 | ]; 27 | 28 | var count = 0; 29 | 30 | var router = Router.run(routes, location, function (Handler) { 31 | count += 1; 32 | 33 | if (count === 2) { 34 | expect(History.length).toEqual(2); 35 | done(); 36 | } 37 | }); 38 | 39 | router.transitionTo('about'); 40 | }); 41 | 42 | describe('that redirects to another route', function () { 43 | it('has length 2', function (done) { 44 | var routes = [ 45 | , 46 | 47 | ]; 48 | 49 | var count = 0; 50 | 51 | var router = Router.run(routes, location, function (Handler) { 52 | count += 1; 53 | 54 | if (count === 2) { 55 | expect(History.length).toEqual(2); 56 | done(); 57 | } 58 | }); 59 | 60 | router.transitionTo('about'); 61 | }); 62 | }); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /modules/__tests__/Routing-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('expect'); 2 | var React = require('react'); 3 | var Router = require('../index'); 4 | var Route = require('../components/Route'); 5 | var { Foo, Bar, Nested } = require('../TestUtils'); 6 | 7 | describe('creating routes from ReactChildren', function () { 8 | it('works with falsy children', function (done) { 9 | var routes = [ 10 | , 11 | null, 12 | , 13 | undefined 14 | ]; 15 | 16 | Router.run(routes, '/foo', function (Handler, state) { 17 | var html = React.renderToString(); 18 | expect(html).toMatch(/Foo/); 19 | done(); 20 | }); 21 | }); 22 | 23 | it('works with comments', function (done) { 24 | var routes = [ 25 | 26 | // This is a comment. 27 | 28 | 29 | ]; 30 | 31 | Router.run(routes, '/bar', function (Handler, state) { 32 | var html = React.renderToString(); 33 | expect(html).toMatch(/Bar/); 34 | done(); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /modules/__tests__/State-test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var React = require('react'); 3 | var Router = require('../index'); 4 | var Route = require('../components/Route'); 5 | var TestLocation = require('../locations/TestLocation'); 6 | var { Foo } = require('../TestUtils'); 7 | 8 | describe('State', function () { 9 | 10 | describe('when a route is active', function () { 11 | describe('and it has no params', function () { 12 | it('is active', function (done) { 13 | var location = new TestLocation([ '/foo' ]); 14 | var div = document.createElement('div'); 15 | var routes = ( 16 | 17 | ); 18 | 19 | var router; 20 | 21 | Router.run(routes, location, function (Handler) { 22 | router = this; 23 | React.render(, div, function () { 24 | assert(router.isActive('foo')); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | }); 30 | 31 | describe('and the right params are given', function () { 32 | var location, router; 33 | var div = document.createElement('div'); 34 | var routes = ; 35 | 36 | beforeEach(function (done) { 37 | location = new TestLocation([ '/products/123/456?search=abc&limit=789' ]); 38 | Router.run(routes, location, function (Handler) { 39 | router = this; 40 | React.render(, div, function () { 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | afterEach(function () { 47 | React.unmountComponentAtNode(div); 48 | }); 49 | 50 | describe('and no query is used', function () { 51 | it('is active', function () { 52 | assert(router.isActive('products', { id: 123, variant: '456' })); 53 | }); 54 | }); 55 | 56 | describe('and a matching query is used', function () { 57 | it('is active', function () { 58 | assert(router.isActive('products', { id: 123 }, { search: 'abc' })); 59 | }); 60 | }); 61 | 62 | describe('but the query does not match', function () { 63 | it('is not active', function () { 64 | assert(router.isActive('products', { id: 123 }, { search: 'def' }) === false); 65 | }); 66 | }); 67 | 68 | describe('and the wrong params are given', function () { 69 | it('is not active', function () { 70 | assert(router.isActive('products', { id: 345 }) === false); 71 | }); 72 | }); 73 | 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /modules/actions/LocationActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Actions that modify the URL. 3 | */ 4 | var LocationActions = { 5 | 6 | /** 7 | * Indicates a new location is being pushed to the history stack. 8 | */ 9 | PUSH: 'push', 10 | 11 | /** 12 | * Indicates the current location should be replaced. 13 | */ 14 | REPLACE: 'replace', 15 | 16 | /** 17 | * Indicates the most recent entry should be removed from the history stack. 18 | */ 19 | POP: 'pop' 20 | 21 | }; 22 | 23 | module.exports = LocationActions; 24 | -------------------------------------------------------------------------------- /modules/behaviors/ImitateBrowserBehavior.js: -------------------------------------------------------------------------------- 1 | var LocationActions = require('../actions/LocationActions'); 2 | 3 | /** 4 | * A scroll behavior that attempts to imitate the default behavior 5 | * of modern browsers. 6 | */ 7 | var ImitateBrowserBehavior = { 8 | 9 | updateScrollPosition: function (position, actionType) { 10 | switch (actionType) { 11 | case LocationActions.PUSH: 12 | case LocationActions.REPLACE: 13 | window.scrollTo(0, 0); 14 | break; 15 | case LocationActions.POP: 16 | if (position) { 17 | window.scrollTo(position.x, position.y); 18 | } else { 19 | window.scrollTo(0, 0); 20 | } 21 | break; 22 | } 23 | } 24 | 25 | }; 26 | 27 | module.exports = ImitateBrowserBehavior; 28 | -------------------------------------------------------------------------------- /modules/behaviors/ScrollToTopBehavior.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A scroll behavior that always scrolls to the top of the page 3 | * after a transition. 4 | */ 5 | var ScrollToTopBehavior = { 6 | 7 | updateScrollPosition: function () { 8 | window.scrollTo(0, 0); 9 | } 10 | 11 | }; 12 | 13 | module.exports = ScrollToTopBehavior; 14 | -------------------------------------------------------------------------------- /modules/components/ContextWrapper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This component is necessary to get around a context warning 3 | * present in React 0.13.0. It sovles this by providing a separation 4 | * between the "owner" and "parent" contexts. 5 | */ 6 | 7 | var React = require('react'); 8 | 9 | class ContextWrapper extends React.Component { 10 | 11 | render() { 12 | return this.props.children; 13 | } 14 | 15 | } 16 | 17 | module.exports = ContextWrapper; 18 | -------------------------------------------------------------------------------- /modules/components/DefaultRoute.js: -------------------------------------------------------------------------------- 1 | var PropTypes = require('../PropTypes'); 2 | var RouteHandler = require('./RouteHandler'); 3 | var Route = require('./Route'); 4 | 5 | /** 6 | * A component is a special kind of that 7 | * renders when its parent matches but none of its siblings do. 8 | * Only one such route may be used at any given level in the 9 | * route hierarchy. 10 | */ 11 | class DefaultRoute extends Route {} 12 | 13 | // TODO: Include these in the above class definition 14 | // once we can use ES7 property initializers. 15 | // https://github.com/babel/babel/issues/619 16 | 17 | DefaultRoute.propTypes = { 18 | name: PropTypes.string, 19 | path: PropTypes.falsy, 20 | children: PropTypes.falsy, 21 | handler: PropTypes.func.isRequired 22 | }; 23 | 24 | DefaultRoute.defaultProps = { 25 | handler: RouteHandler 26 | }; 27 | 28 | module.exports = DefaultRoute; 29 | -------------------------------------------------------------------------------- /modules/components/Link.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var assign = require('react/lib/Object.assign'); 3 | var PropTypes = require('../PropTypes'); 4 | 5 | function isLeftClickEvent(event) { 6 | return event.button === 0; 7 | } 8 | 9 | function isModifiedEvent(event) { 10 | return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); 11 | } 12 | 13 | /** 14 | * components are used to create an element that links to a route. 15 | * When that route is active, the link gets an "active" class name (or the 16 | * value of its `activeClassName` prop). 17 | * 18 | * For example, assuming you have the following route: 19 | * 20 | * 21 | * 22 | * You could use the following component to link to that route: 23 | * 24 | * 25 | * 26 | * In addition to params, links may pass along query string parameters 27 | * using the `query` prop. 28 | * 29 | * 30 | */ 31 | class Link extends React.Component { 32 | 33 | handleClick(event) { 34 | var allowTransition = true; 35 | var clickResult; 36 | 37 | if (this.props.onClick) 38 | clickResult = this.props.onClick(event); 39 | 40 | if (isModifiedEvent(event) || !isLeftClickEvent(event)) 41 | return; 42 | 43 | if (clickResult === false || event.defaultPrevented === true) 44 | allowTransition = false; 45 | 46 | event.preventDefault(); 47 | 48 | if (allowTransition) 49 | this.context.router.transitionTo(this.props.to, this.props.params, this.props.query); 50 | } 51 | 52 | /** 53 | * Returns the value of the "href" attribute to use on the DOM element. 54 | */ 55 | getHref() { 56 | return this.context.router.makeHref(this.props.to, this.props.params, this.props.query); 57 | } 58 | 59 | /** 60 | * Returns the value of the "class" attribute to use on the DOM element, which contains 61 | * the value of the activeClassName property when this is active. 62 | */ 63 | getClassName() { 64 | var className = this.props.className 65 | 66 | if (this.getActiveState()) 67 | className += ` ${ this.props.activeClassName }` 68 | 69 | return className 70 | } 71 | 72 | getActiveState() { 73 | return this.context.router.isActive(this.props.to, this.props.params, this.props.query); 74 | } 75 | 76 | render() { 77 | var props = assign({}, this.props, { 78 | href: this.getHref(), 79 | className: this.getClassName(), 80 | onClick: this.handleClick.bind(this) 81 | }); 82 | 83 | if (props.activeStyle && this.getActiveState()) 84 | props.style = props.activeStyle; 85 | 86 | return React.DOM.a(props, this.props.children); 87 | } 88 | 89 | } 90 | 91 | // TODO: Include these in the above class definition 92 | // once we can use ES7 property initializers. 93 | // https://github.com/babel/babel/issues/619 94 | 95 | Link.contextTypes = { 96 | router: PropTypes.router.isRequired 97 | }; 98 | 99 | Link.propTypes = { 100 | activeClassName: PropTypes.string.isRequired, 101 | to: PropTypes.oneOfType([ PropTypes.string, PropTypes.route ]).isRequired, 102 | params: PropTypes.object, 103 | query: PropTypes.object, 104 | activeStyle: PropTypes.object, 105 | onClick: PropTypes.func 106 | }; 107 | 108 | Link.defaultProps = { 109 | activeClassName: 'active', 110 | className: '' 111 | }; 112 | 113 | module.exports = Link; 114 | -------------------------------------------------------------------------------- /modules/components/NotFoundRoute.js: -------------------------------------------------------------------------------- 1 | var PropTypes = require('../PropTypes'); 2 | var RouteHandler = require('./RouteHandler'); 3 | var Route = require('./Route'); 4 | 5 | /** 6 | * A is a special kind of that 7 | * renders when the beginning of its parent's path matches 8 | * but none of its siblings do, including any . 9 | * Only one such route may be used at any given level in the 10 | * route hierarchy. 11 | */ 12 | class NotFoundRoute extends Route {} 13 | 14 | // TODO: Include these in the above class definition 15 | // once we can use ES7 property initializers. 16 | // https://github.com/babel/babel/issues/619 17 | 18 | NotFoundRoute.propTypes = { 19 | name: PropTypes.string, 20 | path: PropTypes.falsy, 21 | children: PropTypes.falsy, 22 | handler: PropTypes.func.isRequired 23 | }; 24 | 25 | NotFoundRoute.defaultProps = { 26 | handler: RouteHandler 27 | }; 28 | 29 | module.exports = NotFoundRoute; 30 | -------------------------------------------------------------------------------- /modules/components/Redirect.js: -------------------------------------------------------------------------------- 1 | var PropTypes = require('../PropTypes'); 2 | var Route = require('./Route'); 3 | 4 | /** 5 | * A component is a special kind of that always 6 | * redirects to another route when it matches. 7 | */ 8 | class Redirect extends Route {} 9 | 10 | // TODO: Include these in the above class definition 11 | // once we can use ES7 property initializers. 12 | // https://github.com/babel/babel/issues/619 13 | 14 | 15 | Redirect.propTypes = { 16 | path: PropTypes.string, 17 | from: PropTypes.string, // Alias for path. 18 | to: PropTypes.string, 19 | handler: PropTypes.falsy 20 | }; 21 | 22 | // Redirects should not have a default handler 23 | Redirect.defaultProps = {}; 24 | 25 | module.exports = Redirect; 26 | -------------------------------------------------------------------------------- /modules/components/Route.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var invariant = require('react/lib/invariant'); 3 | var PropTypes = require('../PropTypes'); 4 | var RouteHandler = require('./RouteHandler'); 5 | 6 | /** 7 | * components specify components that are rendered to the page when the 8 | * URL matches a given pattern. 9 | * 10 | * Routes are arranged in a nested tree structure. When a new URL is requested, 11 | * the tree is searched depth-first to find a route whose path matches the URL. 12 | * When one is found, all routes in the tree that lead to it are considered 13 | * "active" and their components are rendered into the DOM, nested in the same 14 | * order as they are in the tree. 15 | * 16 | * The preferred way to configure a router is using JSX. The XML-like syntax is 17 | * a great way to visualize how routes are laid out in an application. 18 | * 19 | * var routes = [ 20 | * 21 | * 22 | * 23 | * 24 | * 25 | * ]; 26 | * 27 | * Router.run(routes, function (Handler) { 28 | * React.render(, document.body); 29 | * }); 30 | * 31 | * Handlers for Route components that contain children can render their active 32 | * child route using a element. 33 | * 34 | * var App = React.createClass({ 35 | * render: function () { 36 | * return ( 37 | *
    38 | * 39 | *
    40 | * ); 41 | * } 42 | * }); 43 | * 44 | * If no handler is provided for the route, it will render a matched child route. 45 | */ 46 | class Route extends React.Component { 47 | 48 | render() { 49 | invariant( 50 | false, 51 | '%s elements are for router configuration only and should not be rendered', 52 | this.constructor.name 53 | ); 54 | } 55 | 56 | } 57 | 58 | // TODO: Include these in the above class definition 59 | // once we can use ES7 property initializers. 60 | // https://github.com/babel/babel/issues/619 61 | 62 | Route.propTypes = { 63 | name: PropTypes.string, 64 | path: PropTypes.string, 65 | handler: PropTypes.func, 66 | ignoreScrollBehavior: PropTypes.bool 67 | }; 68 | 69 | Route.defaultProps = { 70 | handler: RouteHandler 71 | }; 72 | 73 | module.exports = Route; 74 | -------------------------------------------------------------------------------- /modules/components/RouteHandler.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ContextWrapper = require('./ContextWrapper') 3 | var assign = require('react/lib/Object.assign'); 4 | var PropTypes = require('../PropTypes'); 5 | 6 | var REF_NAME = '__routeHandler__'; 7 | 8 | /** 9 | * A component renders the active child route handler 10 | * when routes are nested. 11 | */ 12 | class RouteHandler extends React.Component { 13 | 14 | getChildContext() { 15 | return { 16 | routeDepth: this.context.routeDepth + 1 17 | }; 18 | } 19 | 20 | componentDidMount() { 21 | this._updateRouteComponent(this.refs[REF_NAME]); 22 | } 23 | 24 | componentDidUpdate() { 25 | this._updateRouteComponent(this.refs[REF_NAME]); 26 | } 27 | 28 | componentWillUnmount() { 29 | this._updateRouteComponent(null); 30 | } 31 | 32 | _updateRouteComponent(component) { 33 | this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); 34 | } 35 | 36 | getRouteDepth() { 37 | return this.context.routeDepth; 38 | } 39 | 40 | createChildRouteHandler(props) { 41 | var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); 42 | return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; 43 | } 44 | 45 | render() { 46 | var handler = this.createChildRouteHandler(); 47 | //