UNPKG

react-router

Version:
1,849 lines (1,483 loc) • 126 kB
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ReactRouter=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ /** * Actions that modify the URL. */ var LocationActions = { /** * Indicates a location is being setup for the first time. */ SETUP: 'setup', /** * Indicates a new location is being pushed to the history stack. */ PUSH: 'push', /** * Indicates the current location should be replaced. */ REPLACE: 'replace', /** * Indicates the most recent entry should be removed from the history stack. */ POP: 'pop' }; module.exports = LocationActions; },{}],2:[function(_dereq_,module,exports){ var LocationActions = _dereq_('../actions/LocationActions'); /** * A scroll behavior that attempts to imitate the default behavior * of modern browsers. */ var ImitateBrowserBehavior = { updateScrollPosition: function (position, actionType) { switch (actionType) { case LocationActions.PUSH: case LocationActions.REPLACE: window.scrollTo(0, 0); break; case LocationActions.POP: window.scrollTo(position.x, position.y); break; } } }; module.exports = ImitateBrowserBehavior; },{"../actions/LocationActions":1}],3:[function(_dereq_,module,exports){ /** * A scroll behavior that always scrolls to the top of the page * after a transition. */ var ScrollToTopBehavior = { updateScrollPosition: function () { window.scrollTo(0, 0); } }; module.exports = ScrollToTopBehavior; },{}],4:[function(_dereq_,module,exports){ var merge = _dereq_('react/lib/merge'); var Route = _dereq_('./Route'); /** * A <DefaultRoute> component is a special kind of <Route> that * renders when its parent matches but none of its siblings do. * Only one such route may be used at any given level in the * route hierarchy. */ function DefaultRoute(props) { return Route( merge(props, { path: null, isDefault: true }) ); } module.exports = DefaultRoute; },{"./Route":8,"react/lib/merge":45}],5:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var merge = _dereq_('react/lib/merge'); var ActiveState = _dereq_('../mixins/ActiveState'); var Navigation = _dereq_('../mixins/Navigation'); function isLeftClickEvent(event) { return event.button === 0; } function isModifiedEvent(event) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); } /** * <Link> components are used to create an <a> element that links to a route. * When that route is active, the link gets an "active" class name (or the * value of its `activeClassName` prop). * * For example, assuming you have the following route: * * <Route name="showPost" path="/posts/:postID" handler={Post}/> * * You could use the following component to link to that route: * * <Link to="showPost" params={{ postID: "123" }} /> * * In addition to params, links may pass along query string parameters * using the `query` prop. * * <Link to="showPost" params={{ postID: "123" }} query={{ show:true }}/> */ var Link = React.createClass({ displayName: 'Link', mixins: [ ActiveState, Navigation ], propTypes: { activeClassName: React.PropTypes.string.isRequired, to: React.PropTypes.string.isRequired, params: React.PropTypes.object, query: React.PropTypes.object, onClick: React.PropTypes.func }, getDefaultProps: function () { return { activeClassName: 'active' }; }, handleClick: function (event) { var allowTransition = true; var clickResult; if (this.props.onClick) clickResult = this.props.onClick(event); if (isModifiedEvent(event) || !isLeftClickEvent(event)) return; if (clickResult === false || event.defaultPrevented === true) allowTransition = false; event.preventDefault(); if (allowTransition) this.transitionTo(this.props.to, this.props.params, this.props.query); }, /** * Returns the value of the "href" attribute to use on the DOM element. */ getHref: function () { return this.makeHref(this.props.to, this.props.params, this.props.query); }, /** * Returns the value of the "class" attribute to use on the DOM element, which contains * the value of the activeClassName property when this <Link> is active. */ getClassName: function () { var className = this.props.className || ''; if (this.isActive(this.props.to, this.props.params, this.props.query)) className += ' ' + this.props.activeClassName; return className; }, render: function () { var props = merge(this.props, { href: this.getHref(), className: this.getClassName(), onClick: this.handleClick }); return React.DOM.a(props, this.props.children); } }); module.exports = Link; },{"../mixins/ActiveState":16,"../mixins/Navigation":19,"react/lib/merge":45}],6:[function(_dereq_,module,exports){ var merge = _dereq_('react/lib/merge'); var Route = _dereq_('./Route'); /** * A <NotFoundRoute> is a special kind of <Route> that * renders when the beginning of its parent's path matches * but none of its siblings do, including any <DefaultRoute>. * Only one such route may be used at any given level in the * route hierarchy. */ function NotFoundRoute(props) { return Route( merge(props, { path: null, catchAll: true }) ); } module.exports = NotFoundRoute; },{"./Route":8,"react/lib/merge":45}],7:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var Route = _dereq_('./Route'); function createRedirectHandler(to) { return React.createClass({ statics: { willTransitionTo: function (transition, params, query) { transition.redirect(to, params, query); } }, render: function () { return null; } }); } /** * A <Redirect> component is a special kind of <Route> that always * redirects to another route when it matches. */ function Redirect(props) { return Route({ name: props.name, path: props.from || props.path || '*', handler: createRedirectHandler(props.to) }); } module.exports = Redirect; },{"./Route":8}],8:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var withoutProperties = _dereq_('../utils/withoutProperties'); /** * A map of <Route> component props that are reserved for use by the * router and/or React. All other props are considered "static" and * are passed through to the route handler. */ var RESERVED_PROPS = { handler: true, path: true, defaultRoute: true, notFoundRoute: true, paramNames: true, children: true // ReactChildren }; /** * <Route> components specify components that are rendered to the page when the * URL matches a given pattern. * * Routes are arranged in a nested tree structure. When a new URL is requested, * the tree is searched depth-first to find a route whose path matches the URL. * When one is found, all routes in the tree that lead to it are considered * "active" and their components are rendered into the DOM, nested in the same * order as they are in the tree. * * Unlike Ember, a nested route's path does not build upon that of its parents. * This may seem like it creates more work up front in specifying URLs, but it * has the nice benefit of decoupling nested UI from "nested" URLs. * * The preferred way to configure a router is using JSX. The XML-like syntax is * a great way to visualize how routes are laid out in an application. * * React.renderComponent(( * <Routes handler={App}> * <Route name="login" handler={Login}/> * <Route name="logout" handler={Logout}/> * <Route name="about" handler={About}/> * </Routes> * ), document.body); * * If you don't use JSX, you can also assemble a Router programmatically using * the standard React component JavaScript API. * * React.renderComponent(( * Routes({ handler: App }, * Route({ name: 'login', handler: Login }), * Route({ name: 'logout', handler: Logout }), * Route({ name: 'about', handler: About }) * ) * ), document.body); * * Handlers for Route components that contain children can render their active * child route using the activeRouteHandler prop. * * var App = React.createClass({ * render: function () { * return ( * <div class="application"> * {this.props.activeRouteHandler()} * </div> * ); * } * }); */ var Route = React.createClass({ displayName: 'Route', statics: { getUnreservedProps: function (props) { return withoutProperties(props, RESERVED_PROPS); } }, propTypes: { handler: React.PropTypes.any.isRequired, path: React.PropTypes.string, name: React.PropTypes.string }, render: function () { throw new Error( 'The <Route> component should not be rendered directly. You may be ' + 'missing a <Routes> wrapper around your list of routes.' ); } }); module.exports = Route; },{"../utils/withoutProperties":30}],9:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var warning = _dereq_('react/lib/warning'); var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var copyProperties = _dereq_('react/lib/copyProperties'); var PathStore = _dereq_('../stores/PathStore'); var HashLocation = _dereq_('../locations/HashLocation'); var reversedArray = _dereq_('../utils/reversedArray'); var Transition = _dereq_('../utils/Transition'); var Redirect = _dereq_('../utils/Redirect'); var Path = _dereq_('../utils/Path'); var Route = _dereq_('./Route'); function makeMatch(route, params) { return { route: route, params: params }; } function getRootMatch(matches) { return matches[matches.length - 1]; } function findMatches(path, routes, defaultRoute, notFoundRoute) { var matches = null, route, params; for (var i = 0, len = routes.length; i < len; ++i) { route = routes[i]; // Check the subtree first to find the most deeply-nested match. matches = findMatches(path, route.props.children, route.props.defaultRoute, route.props.notFoundRoute); if (matches != null) { var rootParams = getRootMatch(matches).params; params = route.props.paramNames.reduce(function (params, paramName) { params[paramName] = rootParams[paramName]; return params; }, {}); matches.unshift(makeMatch(route, params)); return matches; } // No routes in the subtree matched, so check this route. params = Path.extractParams(route.props.path, path); if (params) return [ makeMatch(route, params) ]; } // No routes matched, so try the default route if there is one. if (defaultRoute && (params = Path.extractParams(defaultRoute.props.path, path))) return [ makeMatch(defaultRoute, params) ]; // Last attempt: does the "not found" route match? if (notFoundRoute && (params = Path.extractParams(notFoundRoute.props.path, path))) return [ makeMatch(notFoundRoute, params) ]; return matches; } function hasMatch(matches, match) { return matches.some(function (m) { if (m.route !== match.route) return false; for (var property in m.params) if (m.params[property] !== match.params[property]) return false; return true; }); } function updateMatchComponents(matches, refs) { var i = 0, component; while (component = refs.__activeRoute__) { matches[i++].component = component; refs = component.refs; } } /** * Computes the next state for the given component and calls * callback(error, nextState) when finished. Also runs all * transition hooks along the way. */ function computeNextState(component, transition, callback) { if (component.state.path === transition.path) return callback(); // Nothing to do! var currentMatches = component.state.matches; var nextMatches = component.match(transition.path); warning( nextMatches, 'No route matches path "' + transition.path + '". Make sure you have ' + '<Route path="' + transition.path + '"> somewhere in your routes' ); if (!nextMatches) nextMatches = []; var fromMatches, toMatches; if (currentMatches.length) { updateMatchComponents(currentMatches, component.refs); fromMatches = currentMatches.filter(function (match) { return !hasMatch(nextMatches, match); }); toMatches = nextMatches.filter(function (match) { return !hasMatch(currentMatches, match); }); } else { fromMatches = []; toMatches = nextMatches; } var query = Path.extractQuery(transition.path) || {}; runTransitionFromHooks(fromMatches, transition, function (error) { if (error || transition.isAborted) return callback(error); runTransitionToHooks(toMatches, transition, query, function (error) { if (error || transition.isAborted) return callback(error); var matches = currentMatches.slice(0, currentMatches.length - fromMatches.length).concat(toMatches); var rootMatch = getRootMatch(matches); var params = (rootMatch && rootMatch.params) || {}; var routes = matches.map(function (match) { return match.route; }); callback(null, { path: transition.path, matches: matches, activeRoutes: routes, activeParams: params, activeQuery: query }); }); }); } /** * Calls the willTransitionFrom hook of all handlers in the given matches * serially in reverse with the transition object and the current instance of * the route's handler, so that the deepest nested handlers are called first. * Calls callback(error) when finished. */ function runTransitionFromHooks(matches, transition, callback) { var hooks = reversedArray(matches).map(function (match) { return function () { var handler = match.route.props.handler; if (!transition.isAborted && handler.willTransitionFrom) return handler.willTransitionFrom(transition, match.component); var promise = transition.promise; delete transition.promise; return promise; }; }); runHooks(hooks, callback); } /** * Calls the willTransitionTo hook of all handlers in the given matches * serially with the transition object and any params that apply to that * handler. Calls callback(error) when finished. */ function runTransitionToHooks(matches, transition, query, callback) { var hooks = matches.map(function (match) { return function () { var handler = match.route.props.handler; if (!transition.isAborted && handler.willTransitionTo) handler.willTransitionTo(transition, match.params, query); var promise = transition.promise; delete transition.promise; return promise; }; }); runHooks(hooks, callback); } /** * Runs all hook functions serially and calls callback(error) when finished. * A hook may return a promise if it needs to execute asynchronously. */ function runHooks(hooks, callback) { try { var promise = hooks.reduce(function (promise, hook) { // The first hook to use transition.wait makes the rest // of the transition async from that point forward. return promise ? promise.then(hook) : hook(); }, null); } catch (error) { return callback(error); // Sync error. } if (promise) { // Use setTimeout to break the promise chain. promise.then(function () { setTimeout(callback); }, function (error) { setTimeout(function () { callback(error); }); }); } else { callback(); } } function returnNull() { return null; } function computeHandlerProps(matches, query) { var handler = returnNull; var props = { ref: null, params: null, query: null, activeRouteHandler: handler, key: null }; reversedArray(matches).forEach(function (match) { var route = match.route; props = Route.getUnreservedProps(route.props); props.ref = '__activeRoute__'; props.params = match.params; props.query = query; props.activeRouteHandler = handler; // TODO: Can we remove addHandlerKey? if (route.props.addHandlerKey) props.key = Path.injectParams(route.props.path, match.params); handler = function (props, addedProps) { if (arguments.length > 2 && typeof arguments[2] !== 'undefined') throw new Error('Passing children to a route handler is not supported'); return route.props.handler( copyProperties(props, addedProps) ); }.bind(this, props); }); return props; } var BrowserTransitionHandling = { handleTransitionError: function (component, error) { throw error; // This error probably originated in a transition hook. }, handleAbortedTransition: function (component, transition) { var reason = transition.abortReason; if (reason instanceof Redirect) { component.replaceWith(reason.to, reason.params, reason.query); } else { component.goBack(); } } }; var ServerTransitionHandling = { handleTransitionError: function (component, error) { // TODO }, handleAbortedTransition: function (component, transition) { // TODO } }; var TransitionHandling = canUseDOM ? BrowserTransitionHandling : ServerTransitionHandling; var ActiveContext = _dereq_('../mixins/ActiveContext'); var LocationContext = _dereq_('../mixins/LocationContext'); var RouteContext = _dereq_('../mixins/RouteContext'); var ScrollContext = _dereq_('../mixins/ScrollContext'); /** * The <Routes> component configures the route hierarchy and renders the * route matching the current location when rendered into a document. * * See the <Route> component for more details. */ var Routes = React.createClass({ displayName: 'Routes', mixins: [ ActiveContext, LocationContext, RouteContext, ScrollContext ], propTypes: { initialPath: React.PropTypes.string, onChange: React.PropTypes.func }, getInitialState: function () { return { matches: [] }; }, componentWillMount: function () { this.handlePathChange(this.props.initialPath); }, componentDidMount: function () { PathStore.addChangeListener(this.handlePathChange); }, componentWillUnmount: function () { PathStore.removeChangeListener(this.handlePathChange); }, handlePathChange: function (_path) { var path = _path || PathStore.getCurrentPath(); var actionType = PathStore.getCurrentActionType(); if (this.state.path === path) return; // Nothing to do! if (this.state.path) this.recordScroll(this.state.path); var self = this; this.dispatch(path, function (error, transition) { if (error) { TransitionHandling.handleTransitionError(self, error); } else if (transition.isAborted) { TransitionHandling.handleAbortedTransition(self, transition); } else { self.updateScroll(path, actionType); if (self.props.onChange) self.props.onChange.call(self); } }); }, /** * Performs a depth-first search for the first route in the tree that matches on * the given path. Returns an array of all routes in the tree leading to the one * that matched in the format { route, params } where params is an object that * contains the URL parameters relevant to that route. Returns null if no route * in the tree matches the path. * * React.renderComponent( * <Routes> * <Route handler={App}> * <Route name="posts" handler={Posts}/> * <Route name="post" path="/posts/:id" handler={Post}/> * </Route> * </Routes> * ).match('/posts/123'); => [ { route: <AppRoute>, params: {} }, * { route: <PostRoute>, params: { id: '123' } } ] */ match: function (path) { return findMatches(Path.withoutQuery(path), this.getRoutes(), this.props.defaultRoute, this.props.notFoundRoute); }, /** * Performs a transition to the given path and calls callback(error, transition) * with the Transition object when the transition is finished and the component's * state has been updated accordingly. * * In a transition, the router first determines which routes are involved by * beginning with the current route, up the route tree to the first parent route * that is shared with the destination route, and back down the tree to the * destination route. The willTransitionFrom hook is invoked on all route handlers * we're transitioning away from, in reverse nesting order. Likewise, the * willTransitionTo hook is invoked on all route handlers we're transitioning to. * * Both willTransitionFrom and willTransitionTo hooks may either abort or redirect * the transition. To resolve asynchronously, they may use transition.wait(promise). */ dispatch: function (path, callback) { var transition = new Transition(this, path); var self = this; computeNextState(this, transition, function (error, nextState) { if (error || nextState == null) return callback(error, transition); self.setState(nextState, function () { callback(null, transition); }); }); }, /** * Returns the props that should be used for the top-level route handler. */ getHandlerProps: function () { return computeHandlerProps(this.state.matches, this.state.activeQuery); }, /** * Returns a reference to the active route handler's component instance. */ getActiveComponent: function () { return this.refs.__activeRoute__; }, /** * Returns the current URL path. */ getCurrentPath: function () { return this.state.path; }, /** * Returns an absolute URL path created from the given route * name, URL parameters, and query values. */ makePath: function (to, params, query) { var path; if (Path.isAbsolute(to)) { path = Path.normalize(to); } else { var namedRoutes = this.getNamedRoutes(); var route = namedRoutes[to]; invariant( route, 'Unable to find a route named "' + to + '". Make sure you have ' + 'a <Route name="' + to + '"> defined somewhere in your <Routes>' ); path = route.props.path; } return Path.withQuery(Path.injectParams(path, params), query); }, /** * Returns a string that may safely be used as the href of a * link to the route with the given name. */ makeHref: function (to, params, query) { var path = this.makePath(to, params, query); if (this.getLocation() === HashLocation) return '#' + path; return path; }, /** * Transitions to the URL specified in the arguments by pushing * a new URL onto the history stack. */ transitionTo: function (to, params, query) { var location = this.getLocation(); invariant( location, 'You cannot use transitionTo without a location' ); location.push(this.makePath(to, params, query)); }, /** * Transitions to the URL specified in the arguments by replacing * the current URL in the history stack. */ replaceWith: function (to, params, query) { var location = this.getLocation(); invariant( location, 'You cannot use replaceWith without a location' ); location.replace(this.makePath(to, params, query)); }, /** * Transitions to the previous URL. */ goBack: function () { var location = this.getLocation(); invariant( location, 'You cannot use goBack without a location' ); location.pop(); }, render: function () { var match = this.state.matches[0]; if (match == null) return null; return match.route.props.handler( this.getHandlerProps() ); }, childContextTypes: { currentPath: React.PropTypes.string, makePath: React.PropTypes.func.isRequired, makeHref: React.PropTypes.func.isRequired, transitionTo: React.PropTypes.func.isRequired, replaceWith: React.PropTypes.func.isRequired, goBack: React.PropTypes.func.isRequired }, getChildContext: function () { return { currentPath: this.getCurrentPath(), makePath: this.makePath, makeHref: this.makeHref, transitionTo: this.transitionTo, replaceWith: this.replaceWith, goBack: this.goBack }; } }); module.exports = Routes; },{"../locations/HashLocation":12,"../mixins/ActiveContext":15,"../mixins/LocationContext":18,"../mixins/RouteContext":20,"../mixins/ScrollContext":21,"../stores/PathStore":22,"../utils/Path":23,"../utils/Redirect":25,"../utils/Transition":26,"../utils/reversedArray":28,"./Route":8,"react/lib/ExecutionEnvironment":40,"react/lib/copyProperties":41,"react/lib/invariant":43,"react/lib/warning":49}],10:[function(_dereq_,module,exports){ var copyProperties = _dereq_('react/lib/copyProperties'); var Dispatcher = _dereq_('flux').Dispatcher; /** * Dispatches actions that modify the URL. */ var LocationDispatcher = copyProperties(new Dispatcher, { handleViewAction: function (action) { this.dispatch({ source: 'VIEW_ACTION', action: action }); } }); module.exports = LocationDispatcher; },{"flux":32,"react/lib/copyProperties":41}],11:[function(_dereq_,module,exports){ exports.DefaultRoute = _dereq_('./components/DefaultRoute'); exports.Link = _dereq_('./components/Link'); exports.NotFoundRoute = _dereq_('./components/NotFoundRoute'); exports.Redirect = _dereq_('./components/Redirect'); exports.Route = _dereq_('./components/Route'); exports.Routes = _dereq_('./components/Routes'); exports.ActiveState = _dereq_('./mixins/ActiveState'); exports.CurrentPath = _dereq_('./mixins/CurrentPath'); exports.Navigation = _dereq_('./mixins/Navigation'); },{"./components/DefaultRoute":4,"./components/Link":5,"./components/NotFoundRoute":6,"./components/Redirect":7,"./components/Route":8,"./components/Routes":9,"./mixins/ActiveState":16,"./mixins/CurrentPath":17,"./mixins/Navigation":19}],12:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var LocationActions = _dereq_('../actions/LocationActions'); var LocationDispatcher = _dereq_('../dispatchers/LocationDispatcher'); var getWindowPath = _dereq_('../utils/getWindowPath'); function getHashPath() { return window.location.hash.substr(1); } var _actionType; function ensureSlash() { var path = getHashPath(); if (path.charAt(0) === '/') return true; HashLocation.replace('/' + path); return false; } function onHashChange() { if (ensureSlash()) { var path = getHashPath(); LocationDispatcher.handleViewAction({ // If we don't have an _actionType then all we know is the hash // changed. It was probably caused by the user clicking the Back // button, but may have also been the Forward button or manual // manipulation. So just guess 'pop'. type: _actionType || LocationActions.POP, path: getHashPath() }); _actionType = null; } } var _isSetup = false; /** * A Location that uses `window.location.hash`. */ var HashLocation = { setup: function () { if (_isSetup) return; invariant( canUseDOM, 'You cannot use HashLocation in an environment with no DOM' ); ensureSlash(); LocationDispatcher.handleViewAction({ type: LocationActions.SETUP, path: getHashPath() }); if (window.addEventListener) { window.addEventListener('hashchange', onHashChange, false); } else { window.attachEvent('onhashchange', onHashChange); } _isSetup = true; }, teardown: function () { if (window.removeEventListener) { window.removeEventListener('hashchange', onHashChange, false); } else { window.detachEvent('onhashchange', onHashChange); } _isSetup = false; }, push: function (path) { _actionType = LocationActions.PUSH; window.location.hash = path; }, replace: function (path) { _actionType = LocationActions.REPLACE; window.location.replace(getWindowPath() + '#' + path); }, pop: function () { _actionType = LocationActions.POP; window.history.back(); }, toString: function () { return '<HashLocation>'; } }; module.exports = HashLocation; },{"../actions/LocationActions":1,"../dispatchers/LocationDispatcher":10,"../utils/getWindowPath":27,"react/lib/ExecutionEnvironment":40,"react/lib/invariant":43}],13:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var LocationActions = _dereq_('../actions/LocationActions'); var LocationDispatcher = _dereq_('../dispatchers/LocationDispatcher'); var getWindowPath = _dereq_('../utils/getWindowPath'); function onPopState() { LocationDispatcher.handleViewAction({ type: LocationActions.POP, path: getWindowPath() }); } var _isSetup = false; /** * A Location that uses HTML5 history. */ var HistoryLocation = { setup: function () { if (_isSetup) return; invariant( canUseDOM, 'You cannot use HistoryLocation in an environment with no DOM' ); LocationDispatcher.handleViewAction({ type: LocationActions.SETUP, path: getWindowPath() }); if (window.addEventListener) { window.addEventListener('popstate', onPopState, false); } else { window.attachEvent('popstate', onPopState); } _isSetup = true; }, teardown: function () { if (window.removeEventListener) { window.removeEventListener('popstate', onPopState, false); } else { window.detachEvent('popstate', onPopState); } _isSetup = false; }, push: function (path) { window.history.pushState({ path: path }, '', path); LocationDispatcher.handleViewAction({ type: LocationActions.PUSH, path: getWindowPath() }); }, replace: function (path) { window.history.replaceState({ path: path }, '', path); LocationDispatcher.handleViewAction({ type: LocationActions.REPLACE, path: getWindowPath() }); }, pop: function () { window.history.back(); }, toString: function () { return '<HistoryLocation>'; } }; module.exports = HistoryLocation; },{"../actions/LocationActions":1,"../dispatchers/LocationDispatcher":10,"../utils/getWindowPath":27,"react/lib/ExecutionEnvironment":40,"react/lib/invariant":43}],14:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var LocationActions = _dereq_('../actions/LocationActions'); var LocationDispatcher = _dereq_('../dispatchers/LocationDispatcher'); var getWindowPath = _dereq_('../utils/getWindowPath'); /** * A Location that uses full page refreshes. This is used as * the fallback for HistoryLocation in browsers that do not * support the HTML5 history API. */ var RefreshLocation = { setup: function () { invariant( canUseDOM, 'You cannot use RefreshLocation in an environment with no DOM' ); LocationDispatcher.handleViewAction({ type: LocationActions.SETUP, path: getWindowPath() }); }, push: function (path) { window.location = path; }, replace: function (path) { window.location.replace(path); }, pop: function () { window.history.back(); }, toString: function () { return '<RefreshLocation>'; } }; module.exports = RefreshLocation; },{"../actions/LocationActions":1,"../dispatchers/LocationDispatcher":10,"../utils/getWindowPath":27,"react/lib/ExecutionEnvironment":40,"react/lib/invariant":43}],15:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var copyProperties = _dereq_('react/lib/copyProperties'); function routeIsActive(activeRoutes, routeName) { return activeRoutes.some(function (route) { return route.props.name === routeName; }); } function paramsAreActive(activeParams, params) { for (var property in params) if (String(activeParams[property]) !== String(params[property])) return false; return true; } function queryIsActive(activeQuery, query) { for (var property in query) if (String(activeQuery[property]) !== String(query[property])) return false; return true; } /** * A mixin for components that store the active state of routes, URL * parameters, and query. */ var ActiveContext = { propTypes: { initialActiveState: React.PropTypes.object }, getDefaultProps: function () { return { initialActiveState: {} }; }, getInitialState: function () { var state = this.props.initialActiveState; return { activeRoutes: state.activeRoutes || [], activeParams: state.activeParams || {}, activeQuery: state.activeQuery || {} }; }, /** * Returns a read-only array of the currently active routes. */ getActiveRoutes: function () { return this.state.activeRoutes.slice(0); }, /** * Returns a read-only object of the currently active URL parameters. */ getActiveParams: function () { return copyProperties({}, this.state.activeParams); }, /** * Returns a read-only object of the currently active query parameters. */ getActiveQuery: function () { return copyProperties({}, this.state.activeQuery); }, /** * Returns true if the route with the given name, URL parameters, and * query are all currently active. */ isActive: function (routeName, params, query) { var isActive = routeIsActive(this.state.activeRoutes, routeName) && paramsAreActive(this.state.activeParams, params); if (query) return isActive && queryIsActive(this.state.activeQuery, query); return isActive; }, childContextTypes: { activeRoutes: React.PropTypes.array.isRequired, activeParams: React.PropTypes.object.isRequired, activeQuery: React.PropTypes.object.isRequired, isActive: React.PropTypes.func.isRequired }, getChildContext: function () { return { activeRoutes: this.getActiveRoutes(), activeParams: this.getActiveParams(), activeQuery: this.getActiveQuery(), isActive: this.isActive }; } }; module.exports = ActiveContext; },{"react/lib/copyProperties":41}],16:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); /** * A mixin for components that need to know the routes, URL * params and query that are currently active. */ var ActiveState = { contextTypes: { activeRoutes: React.PropTypes.array.isRequired, activeParams: React.PropTypes.object.isRequired, activeQuery: React.PropTypes.object.isRequired, isActive: React.PropTypes.func.isRequired }, /** * Returns an array of the routes that are currently active. */ getActiveRoutes: function () { return this.context.activeRoutes; }, /** * Returns an object of the URL params that are currently active. */ getActiveParams: function () { return this.context.activeParams; }, /** * Returns an object of the query params that are currently active. */ getActiveQuery: function () { return this.context.activeQuery; }, /** * A helper method to determine if a given route, params, and query * are active. */ isActive: function (to, params, query) { return this.context.isActive(to, params, query); } }; module.exports = ActiveState; },{}],17:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); /** * A mixin for components that need to know the current URL path. * * Example: * * var ShowThePath = React.createClass({ * mixins: [ Router.CurrentPath ], * render: function () { * return ( * <div>The current path is: {this.getCurrentPath()}</div> * ); * } * }); */ var CurrentPath = { contextTypes: { currentPath: React.PropTypes.string.isRequired }, /** * Returns the current URL path. */ getCurrentPath: function () { return this.context.currentPath; } }; module.exports = CurrentPath; },{}],18:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var HashLocation = _dereq_('../locations/HashLocation'); var HistoryLocation = _dereq_('../locations/HistoryLocation'); var RefreshLocation = _dereq_('../locations/RefreshLocation'); var supportsHistory = _dereq_('../utils/supportsHistory'); /** * A hash of { name: location } pairs. */ var NAMED_LOCATIONS = { none: null, hash: HashLocation, history: HistoryLocation, refresh: RefreshLocation }; /** * A mixin for components that manage location. */ var LocationContext = { propTypes: { location: function (props, propName, componentName) { var location = props[propName]; if (typeof location === 'string' && !(location in NAMED_LOCATIONS)) return new Error('Unknown location "' + location + '", see ' + componentName); } }, getDefaultProps: function () { return { location: canUseDOM ? HashLocation : null }; }, getInitialState: function () { var location = this.props.location; if (typeof location === 'string') location = NAMED_LOCATIONS[location]; // Automatically fall back to full page refreshes in // browsers that do not support HTML5 history. if (location === HistoryLocation && !supportsHistory()) location = RefreshLocation; return { location: location }; }, componentWillMount: function () { var location = this.getLocation(); invariant( location == null || canUseDOM, 'Cannot use location without a DOM' ); if (location && location.setup) location.setup(); }, /** * Returns the location object this component uses. */ getLocation: function () { return this.state.location; }, childContextTypes: { location: React.PropTypes.object // Not required on the server. }, getChildContext: function () { return { location: this.getLocation() }; } }; module.exports = LocationContext; },{"../locations/HashLocation":12,"../locations/HistoryLocation":13,"../locations/RefreshLocation":14,"../utils/supportsHistory":29,"react/lib/ExecutionEnvironment":40,"react/lib/invariant":43}],19:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); /** * A mixin for components that modify the URL. */ var Navigation = { contextTypes: { makePath: React.PropTypes.func.isRequired, makeHref: React.PropTypes.func.isRequired, transitionTo: React.PropTypes.func.isRequired, replaceWith: React.PropTypes.func.isRequired, goBack: React.PropTypes.func.isRequired }, /** * Returns an absolute URL path created from the given route * name, URL parameters, and query values. */ makePath: function (to, params, query) { return this.context.makePath(to, params, query); }, /** * Returns a string that may safely be used as the href of a * link to the route with the given name. */ makeHref: function (to, params, query) { return this.context.makeHref(to, params, query); }, /** * Transitions to the URL specified in the arguments by pushing * a new URL onto the history stack. */ transitionTo: function (to, params, query) { this.context.transitionTo(to, params, query); }, /** * Transitions to the URL specified in the arguments by replacing * the current URL in the history stack. */ replaceWith: function (to, params, query) { this.context.replaceWith(to, params, query); }, /** * Transitions to the previous URL. */ goBack: function () { this.context.goBack(); } }; module.exports = Navigation; },{}],20:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var invariant = _dereq_('react/lib/invariant'); var Path = _dereq_('../utils/Path'); /** * Performs some normalization and validation on a <Route> component and * all of its children. */ function processRoute(route, container, namedRoutes) { // Note: parentRoute may be a <Route> _or_ a <Routes>. var props = route.props; invariant( React.isValidClass(props.handler), 'The handler for the "%s" route must be a valid React class', props.name || props.path ); var parentPath = (container && container.props.path) || '/'; if ((props.path || props.name) && !props.isDefault && !props.catchAll) { var path = props.path || props.name; // Relative paths extend their parent. if (!Path.isAbsolute(path)) path = Path.join(parentPath, path); props.path = Path.normalize(path); } else { props.path = parentPath; if (props.catchAll) props.path += '*'; } props.paramNames = Path.extractParamNames(props.path); // Make sure the route's path has all params its parent needs. if (container && Array.isArray(container.props.paramNames)) { container.props.paramNames.forEach(function (paramName) { invariant( props.paramNames.indexOf(paramName) !== -1, 'The nested route path "%s" is missing the "%s" parameter of its parent path "%s"', props.path, paramName, container.props.path ); }); } // Make sure the route can be looked up by <Link>s. if (props.name) { var existingRoute = namedRoutes[props.name]; invariant( !existingRoute || route === existingRoute, 'You cannot use the name "%s" for more than one route', props.name ); namedRoutes[props.name] = route; } // Handle <NotFoundRoute>. if (props.catchAll) { invariant( container, '<NotFoundRoute> must have a parent <Route>' ); invariant( container.props.notFoundRoute == null, 'You may not have more than one <NotFoundRoute> per <Route>' ); container.props.notFoundRoute = route; return null; } // Handle <DefaultRoute>. if (props.isDefault) { invariant( container, '<DefaultRoute> must have a parent <Route>' ); invariant( container.props.defaultRoute == null, 'You may not have more than one <DefaultRoute> per <Route>' ); container.props.defaultRoute = route; return null; } // Make sure children is an array. props.children = processRoutes(props.children, route, namedRoutes); return route; } /** * Processes many children <Route>s at once, always returning an array. */ function processRoutes(children, container, namedRoutes) { var routes = []; React.Children.forEach(children, function (child) { // Exclude <DefaultRoute>s and <NotFoundRoute>s. if (child = processRoute(child, container, namedRoutes)) routes.push(child); }); return routes; } /** * A mixin for components that have <Route> children. */ var RouteContext = { getInitialState: function () { var namedRoutes = {}; return { routes: processRoutes(this.props.children, this, namedRoutes), namedRoutes: namedRoutes }; }, /** * Returns an array of <Route>s in this container. */ getRoutes: function () { return this.state.routes; }, /** * Returns a hash { name: route } of all named <Route>s in this container. */ getNamedRoutes: function () { return this.state.namedRoutes; }, /** * Returns the route with the given name. */ getRouteByName: function (routeName) { return this.state.namedRoutes[routeName] || null; }, childContextTypes: { routes: React.PropTypes.array.isRequired, namedRoutes: React.PropTypes.object.isRequired }, getChildContext: function () { return { routes: this.getRoutes(), namedRoutes: this.getNamedRoutes(), }; } }; module.exports = RouteContext; },{"../utils/Path":23,"react/lib/invariant":43}],21:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var invariant = _dereq_('react/lib/invariant'); var canUseDOM = _dereq_('react/lib/ExecutionEnvironment').canUseDOM; var ImitateBrowserBehavior = _dereq_('../behaviors/ImitateBrowserBehavior'); var ScrollToTopBehavior = _dereq_('../behaviors/ScrollToTopBehavior'); function getWindowScrollPosition() { invariant( canUseDOM, 'Cannot get current scroll position without a DOM' ); return { x: window.scrollX, y: window.scrollY }; } /** * A hash of { name: scrollBehavior } pairs. */ var NAMED_SCROLL_BEHAVIORS = { none: null, browser: ImitateBrowserBehavior, imitateBrowser: ImitateBrowserBehavior, scrollToTop: ScrollToTopBehavior }; /** * A mixin for components that manage scroll position. */ var ScrollContext = { propTypes: { scrollBehavior: function (props, propName, componentName) { var behavior = props[propName]; if (typeof behavior === 'string' && !(behavior in NAMED_SCROLL_BEHAVIORS)) return new Error('Unknown scroll behavior "' + behavior + '", see ' + componentName); } }, getDefaultProps: function () { return { scrollBehavior: canUseDOM ? ImitateBrowserBehavior : null }; }, getInitialState: function () { var behavior = this.props.scrollBehavior; if (typeof behavior === 'string') behavior = NAMED_SCROLL_BEHAVIORS[behavior]; return { scrollBehavior: behavior }; }, componentWillMount: function () { var behavior = this.getScrollBehavior(); invariant( behavior == null || canUseDOM, 'Cannot use scroll behavior without a DOM' ); if (behavior != null) this._scrollPositions = {}; }, recordScroll: function (path) { if (this._scrollPositions) this._scrollPositions[path] = getWindowScrollPosition(); }, updateScroll: function (path, actionType) { var behavior = this.getScrollBehavior(); var position = this._scrollPositions[path]; if (behavior && position) behavior.updateScrollPosition(position, actionType); }, /** * Returns the scroll behavior object this component uses. */ getScrollBehavior: function () { return this.state.scrollBehavior; }, childContextTypes: { scrollBehavior: React.PropTypes.object // Not required on the server. }, getChildContext: function () { return { scrollBehavior: this.getScrollBehavior() }; } }; module.exports = ScrollContext; },{"../behaviors/ImitateBrowserBehavior":2,"../behaviors/ScrollToTopBehavior":3,"react/lib/ExecutionEnvironment":40,"react/lib/invariant":43}],22:[function(_dereq_,module,exports){ var EventEmitter = _dereq_('events').EventEmitter; var LocationActions = _dereq_('../actions/LocationActions'); var LocationDispatcher = _dereq_('../dispatchers/LocationDispatcher'); var CHANGE_EVENT = 'change'; var _events = new EventEmitter; function notifyChange() { _events.emit(CHANGE_EVENT); } var _currentPath, _currentActionType; /** * The PathStore keeps track of the current URL path. */ var PathStore = { addChangeListener: function (listener) { _events.addListener(CHANGE_EVENT, listener); }, removeChangeListener: function (listener) { _events.removeListener(CHANGE_EVENT, listener); }, removeAllChangeListeners: function () { _events.removeAllListeners(CHANGE_EVENT); }, /** * Returns the current URL path. */ getCurrentPath: function () { return _currentPath; }, /** * Returns the type of the action that changed the URL. */ getCurrentActionType: function () { return _currentActionType; }, dispatchToken: LocationDispatcher.register(function (payload) { var action = payload.action; switch (action.type) { case LocationActions.SETUP: case LocationActions.PUSH: case LocationActions.REPLACE: case LocationActions.POP: if (_currentPath !== action.path) { _currentPath = action.path; _currentActionType = action.type; notifyChange(); } break; } }) }; module.exports = PathStore; },{"../actions/LocationActions":1,"../dispatchers/LocationDispatcher":10,"events":31}],23:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var merge = _dereq_('qs/lib/utils').merge; var qs = _dereq_('qs'); function encodeURL(url) { return encodeURIComponent(url).replace(/%20/g, '+'); } function decode