UNPKG

react-router

Version:

A complete routing library for React.js

1,743 lines (1,425 loc) • 84.7 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){ module.exports = _dereq_('./modules/mixins/ActiveState'); },{"./modules/mixins/ActiveState":24}],2:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/mixins/AsyncState'); },{"./modules/mixins/AsyncState":25}],3:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/components/Link'); },{"./modules/components/Link":10}],4:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/components/Redirect'); },{"./modules/components/Redirect":11}],5:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/components/Route'); },{"./modules/components/Route":12}],6:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/components/Routes'); },{"./modules/components/Routes":13}],7:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/helpers/goBack'); },{"./modules/helpers/goBack":16}],8:[function(_dereq_,module,exports){ exports.ActiveState = _dereq_('./ActiveState'); exports.AsyncState = _dereq_('./AsyncState'); exports.Link = _dereq_('./Link'); exports.Redirect = _dereq_('./Redirect'); exports.Route = _dereq_('./Route'); exports.Routes = _dereq_('./Routes'); exports.goBack = _dereq_('./goBack'); exports.replaceWith = _dereq_('./replaceWith'); exports.transitionTo = _dereq_('./transitionTo'); exports.makeHref = _dereq_('./makeHref'); },{"./ActiveState":1,"./AsyncState":2,"./Link":3,"./Redirect":4,"./Route":5,"./Routes":6,"./goBack":7,"./makeHref":9,"./replaceWith":62,"./transitionTo":63}],9:[function(_dereq_,module,exports){ module.exports = _dereq_('./modules/helpers/makeHref'); },{"./modules/helpers/makeHref":17}],10:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var ActiveState = _dereq_('../mixins/ActiveState'); var withoutProperties = _dereq_('../helpers/withoutProperties'); var transitionTo = _dereq_('../helpers/transitionTo'); var makeHref = _dereq_('../helpers/makeHref'); var hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty); /** * A map of <Link> component props that are reserved for use by the * router and/or React. All other props are used as params that are * interpolated into the link's path. */ var RESERVED_PROPS = { to: true, className: true, activeClassName: true, query: true, children: true // ReactChildren }; /** * <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" postId="123"/> * * In addition to params, links may pass along query string parameters * using the `query` prop. * * <Link to="showPost" postId="123" query={{show:true}}/> */ var Link = React.createClass({ displayName: 'Link', mixins: [ ActiveState ], statics: { getUnreservedProps: function (props) { return withoutProperties(props, RESERVED_PROPS); } }, propTypes: { to: React.PropTypes.string.isRequired, activeClassName: React.PropTypes.string.isRequired, query: React.PropTypes.object }, getDefaultProps: function () { return { activeClassName: 'active' }; }, getInitialState: function () { return { isActive: false }; }, /** * Returns a hash of URL parameters to use in this <Link>'s path. */ getParams: function () { return Link.getUnreservedProps(this.props); }, /** * Returns the value of the "href" attribute to use on the DOM element. */ getHref: function () { return makeHref(this.props.to, this.getParams(), 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.state.isActive) return className + ' ' + this.props.activeClassName; return className; }, componentWillReceiveProps: function (nextProps) { var params = Link.getUnreservedProps(nextProps); this.setState({ isActive: Link.isActive(nextProps.to, params, nextProps.query) }); }, updateActiveState: function () { this.setState({ isActive: Link.isActive(this.props.to, this.getParams(), this.props.query) }); }, handleClick: function (event) { if (isModifiedEvent(event) || !isLeftClick(event)) return; event.preventDefault(); transitionTo(this.props.to, this.getParams(), this.props.query); }, render: function () { var props = { href: this.getHref(), className: this.getClassName(), onClick: this.handleClick }; // pull in props without overriding for (var propName in this.props) { if (hasOwn(this.props, propName) && hasOwn(props, propName) === false) { props[propName] = this.props[propName]; } } return React.DOM.a(props, this.props.children); } }); function isLeftClick(event) { return event.button === 0; } function isModifiedEvent(event) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); } module.exports = Link; },{"../helpers/makeHref":17,"../helpers/transitionTo":22,"../helpers/withoutProperties":23,"../mixins/ActiveState":24}],11:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var Route = _dereq_('./Route'); function Redirect(props) { return Route({ path: props.from, handler: createRedirectClass(props.to) }); } function createRedirectClass(to) { return React.createClass({ statics: { willTransitionTo: function(transition, params, query) { transition.redirect(to, params, query); } }, render: function() { return null; } }); } module.exports = Redirect; },{"./Route":12}],12:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var withoutProperties = _dereq_('../helpers/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, 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); }, }, getDefaultProps: function() { return { preserveScrollPosition: false }; }, propTypes: { handler: React.PropTypes.any.isRequired, path: React.PropTypes.string, name: React.PropTypes.string, preserveScrollPosition: React.PropTypes.bool }, 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; },{"../helpers/withoutProperties":23}],13:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var warning = _dereq_('react/lib/warning'); var ExecutionEnvironment = _dereq_('react/lib/ExecutionEnvironment'); var mergeProperties = _dereq_('../helpers/mergeProperties'); var goBack = _dereq_('../helpers/goBack'); var replaceWith = _dereq_('../helpers/replaceWith'); var transitionTo = _dereq_('../helpers/transitionTo'); var Route = _dereq_('../components/Route'); var Path = _dereq_('../helpers/Path'); var ActiveStore = _dereq_('../stores/ActiveStore'); var RouteStore = _dereq_('../stores/RouteStore'); var URLStore = _dereq_('../stores/URLStore'); var Promise = _dereq_('es6-promise').Promise; /** * The ref name that can be used to reference the active route component. */ var REF_NAME = '__activeRoute__'; /** * 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', statics: { /** * Handles errors that were thrown asynchronously. By default, the * error is re-thrown so we don't swallow them silently. */ handleAsyncError: function (error, route) { throw error; // This error probably originated in a transition hook. }, /** * Handles cancelled transitions. By default, redirects replace the * current URL and aborts roll it back. */ handleCancelledTransition: function (transition, routes) { var reason = transition.cancelReason; if (reason instanceof Redirect) { replaceWith(reason.to, reason.params, reason.query); } else if (reason instanceof Abort) { goBack(); } } }, propTypes: { location: React.PropTypes.oneOf([ 'hash', 'history' ]).isRequired, preserveScrollPosition: React.PropTypes.bool }, getDefaultProps: function () { return { location: 'hash', preserveScrollPosition: false }; }, getInitialState: function () { return {}; }, componentWillMount: function () { React.Children.forEach(this.props.children, function (child) { RouteStore.registerRoute(child); }); if (!URLStore.isSetup() && ExecutionEnvironment.canUseDOM) URLStore.setup(this.props.location); URLStore.addChangeListener(this.handleRouteChange); }, componentDidMount: function () { this.dispatch(URLStore.getCurrentPath()); }, componentWillUnmount: function () { URLStore.removeChangeListener(this.handleRouteChange); }, handleRouteChange: function () { this.dispatch(URLStore.getCurrentPath()); }, /** * 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) { var rootRoutes = this.props.children; if (!Array.isArray(rootRoutes)) { rootRoutes = [rootRoutes]; } var matches = null; for (var i = 0; matches == null && i < rootRoutes.length; i++) { matches = findMatches(Path.withoutQuery(path), rootRoutes[i]); } return matches; }, /** * Performs a transition to the given path and returns a promise for the * Transition object that was used. * * In order to do this, the router first determines which routes are involved * in the transition 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 static * method is invoked on all route handlers we're transitioning away from, in * reverse nesting order. Likewise, the willTransitionTo static method * is invoked on all route handlers we're transitioning to. * * Both willTransitionFrom and willTransitionTo hooks may either abort or * redirect the transition. If they need to resolve asynchronously, they may * return a promise. * * Any error that occurs asynchronously during the transition is re-thrown in * the top-level scope unless returnRejectedPromise is true, in which case a * rejected promise is returned so the caller may handle the error. * * Note: This function does not update the URL in a browser's location bar. * If you want to keep the URL in sync with transitions, use Router.transitionTo, * Router.replaceWith, or Router.goBack instead. */ dispatch: function (path, returnRejectedPromise) { var transition = new Transition(path); var routes = this; var promise = syncWithTransition(routes, transition).then(function (newState) { if (transition.isCancelled) { Routes.handleCancelledTransition(transition, routes); } else if (newState) { ActiveStore.updateState(newState); } return transition; }); if (!returnRejectedPromise) { promise = promise.then(undefined, function (error) { // Use setTimeout to break the promise chain. setTimeout(function () { Routes.handleAsyncError(error, routes); }); }); } return promise; }, render: function () { if (!this.state.path) return null; var matches = this.state.matches; if (matches.length) { // matches[0] corresponds to the top-most match return matches[0].route.props.handler(computeHandlerProps(matches, this.state.activeQuery)); } else { return null; } } }); function Transition(path) { this.path = path; this.cancelReason = null; this.isCancelled = false; } mergeProperties(Transition.prototype, { abort: function () { this.cancelReason = new Abort(); this.isCancelled = true; }, redirect: function (to, params, query) { this.cancelReason = new Redirect(to, params, query); this.isCancelled = true; }, retry: function () { transitionTo(this.path); } }); function Abort() {} function Redirect(to, params, query) { this.to = to; this.params = params; this.query = query; } function findMatches(path, route) { var children = route.props.children, matches; var params; // Check the subtree first to find the most deeply-nested match. if (Array.isArray(children)) { for (var i = 0, len = children.length; matches == null && i < len; ++i) { matches = findMatches(path, children[i]); } } else if (children) { matches = findMatches(path, children); } if (matches) { var rootParams = getRootMatch(matches).params; params = {}; Path.extractParamNames(route.props.path).forEach(function (paramName) { params[paramName] = rootParams[paramName]; }); 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) ]; return null; } function makeMatch(route, params) { return { route: route, params: params }; } 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 getRootMatch(matches) { return matches[matches.length - 1]; } function updateMatchComponents(matches, refs) { var i = 0, component; while (component = refs[REF_NAME]) { matches[i++].component = component; refs = component.refs; } } /** * Runs all transition hooks that are required to get from the current state * to the state specified by the given transition and updates the current state * if they all pass successfully. Returns a promise that resolves to the new * state if it needs to be updated, or undefined if not. */ function syncWithTransition(routes, transition) { if (routes.state.path === transition.path) return Promise.resolve(); // Nothing to do! var currentMatches = routes.state.matches; var nextMatches = routes.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) { updateMatchComponents(currentMatches, routes.refs); fromMatches = currentMatches.filter(function (match) { return !hasMatch(nextMatches, match); }); toMatches = nextMatches.filter(function (match) { return !hasMatch(currentMatches, match); }); } else { fromMatches = []; toMatches = nextMatches; } return checkTransitionFromHooks(fromMatches, transition).then(function () { if (transition.isCancelled) return; // No need to continue. return checkTransitionToHooks(toMatches, transition).then(function () { if (transition.isCancelled) return; // No need to continue. var rootMatch = getRootMatch(nextMatches); var params = (rootMatch && rootMatch.params) || {}; var query = Path.extractQuery(transition.path) || {}; var state = { path: transition.path, matches: nextMatches, activeParams: params, activeQuery: query, activeRoutes: nextMatches.map(function (match) { return match.route; }) }; // TODO: add functional test maybeScrollWindow(routes, toMatches[toMatches.length - 1]); routes.setState(state); return state; }); }); } /** * 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. * Returns a promise that resolves after the last handler. */ function checkTransitionFromHooks(matches, transition) { var promise = Promise.resolve(); reversedArray(matches).forEach(function (match) { promise = promise.then(function () { var handler = match.route.props.handler; if (!transition.isCancelled && handler.willTransitionFrom) return handler.willTransitionFrom(transition, match.component); }); }); return promise; } /** * Calls the willTransitionTo hook of all handlers in the given matches serially * with the transition object and any params that apply to that handler. Returns * a promise that resolves after the last handler. */ function checkTransitionToHooks(matches, transition) { var promise = Promise.resolve(); matches.forEach(function (match, index) { promise = promise.then(function () { var handler = match.route.props.handler; if (!transition.isCancelled && handler.willTransitionTo) return handler.willTransitionTo(transition, match.params); }); }); return promise; } /** * Given an array of matches as returned by findMatches, return a descriptor for * the handler hierarchy specified by the route. */ function computeHandlerProps(matches, query) { var props = { ref: null, key: null, params: null, query: null, activeRouteHandler: returnNull }; var childHandler; reversedArray(matches).forEach(function (match) { var route = match.route; props = Route.getUnreservedProps(route.props); props.ref = REF_NAME; props.key = Path.injectParams(route.props.path, match.params); props.params = match.params; props.query = query; if (childHandler) { props.activeRouteHandler = childHandler; } else { props.activeRouteHandler = returnNull; } childHandler = 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(mergeProperties(props, addedProps)); }.bind(this, props); }); return props; } function returnNull() { return null; } function reversedArray(array) { return array.slice(0).reverse(); } function maybeScrollWindow(routes, match) { if (routes.props.preserveScrollPosition) return; if (!match || match.route.props.preserveScrollPosition) return; window.scrollTo(0, 0); } module.exports = Routes; },{"../components/Route":12,"../helpers/Path":14,"../helpers/goBack":16,"../helpers/mergeProperties":19,"../helpers/replaceWith":20,"../helpers/transitionTo":22,"../stores/ActiveStore":26,"../stores/RouteStore":27,"../stores/URLStore":28,"es6-promise":32,"react/lib/ExecutionEnvironment":57,"react/lib/warning":61}],14:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var qs = _dereq_('querystring'); var mergeProperties = _dereq_('./mergeProperties'); var URL = _dereq_('./URL'); var paramMatcher = /((?::[a-z_$][a-z0-9_$]*)|\*)/ig; var queryMatcher = /\?(.+)/; function getParamName(pathSegment) { return pathSegment === '*' ? 'splat' : pathSegment.substr(1); } var _compiledPatterns = {}; function compilePattern(pattern) { if (_compiledPatterns[pattern]) return _compiledPatterns[pattern]; var compiled = _compiledPatterns[pattern] = {}; var paramNames = compiled.paramNames = []; var source = pattern.replace(paramMatcher, function (match, pathSegment) { paramNames.push(getParamName(pathSegment)); return pathSegment === '*' ? '(.*?)' : '([^/?#]+)'; }); compiled.matcher = new RegExp('^' + source + '$', 'i'); return compiled; } function isDynamicPattern(pattern) { return pattern.indexOf(':') !== -1 || pattern.indexOf('*') !== -1; } var Path = { /** * Extracts the portions of the given URL path that match the given pattern * and returns an object of param name => value pairs. Returns null if the * pattern does not match the given path. */ extractParams: function (pattern, path) { if (!pattern) return null; if (!isDynamicPattern(pattern)) { if (pattern === URL.decode(path)) return {}; // No dynamic segments, but the paths match. return null; } var compiled = compilePattern(pattern); var match = URL.decode(path).match(compiled.matcher); if (!match) return null; var params = {}; compiled.paramNames.forEach(function (paramName, index) { params[paramName] = match[index + 1]; }); return params; }, /** * Returns an array of the names of all parameters in the given pattern. */ extractParamNames: function (pattern) { if (!pattern) return []; return compilePattern(pattern).paramNames; }, /** * Returns a version of the given route path with params interpolated. Throws * if there is a dynamic segment of the route path for which there is no param. */ injectParams: function (pattern, params) { if (!pattern) return null; if (!isDynamicPattern(pattern)) return pattern; params = params || {}; return pattern.replace(paramMatcher, function (match, pathSegment) { var paramName = getParamName(pathSegment); invariant( params[paramName] != null, 'Missing "' + paramName + '" parameter for path "' + pattern + '"' ); // Preserve forward slashes. return String(params[paramName]).split('/').map(URL.encode).join('/'); }); }, /** * Returns an object that is the result of parsing any query string contained in * the given path, null if the path contains no query string. */ extractQuery: function (path) { var match = path.match(queryMatcher); return match && qs.parse(match[1]); }, /** * Returns a version of the given path without the query string. */ withoutQuery: function (path) { return path.replace(queryMatcher, ''); }, /** * Returns a version of the given path with the parameters in the given query * added to the query string. */ withQuery: function (path, query) { var existingQuery = Path.extractQuery(path); if (existingQuery) query = query ? mergeProperties(existingQuery, query) : existingQuery; var queryString = query && qs.stringify(query); if (queryString) return Path.withoutQuery(path) + '?' + queryString; return path; }, /** * Returns a normalized version of the given path. */ normalize: function (path) { return path.replace(/^\/*/, '/'); } }; module.exports = Path; },{"./URL":15,"./mergeProperties":19,"querystring":31,"react/lib/invariant":60}],15:[function(_dereq_,module,exports){ var urlEncodedSpaceRE = /\+/g; var encodedSpaceRE = /%20/g; var URL = { /* These functions were copied from the https://github.com/cujojs/rest source, MIT licensed */ decode: function (str) { // spec says space should be encoded as '+' str = str.replace(urlEncodedSpaceRE, ' '); return decodeURIComponent(str); }, encode: function (str) { str = encodeURIComponent(str); // spec says space should be encoded as '+' return str.replace(encodedSpaceRE, '+'); } }; module.exports = URL; },{}],16:[function(_dereq_,module,exports){ var URLStore = _dereq_('../stores/URLStore'); function goBack() { URLStore.back(); } module.exports = goBack; },{"../stores/URLStore":28}],17:[function(_dereq_,module,exports){ var URLStore = _dereq_('../stores/URLStore'); var makePath = _dereq_('./makePath'); /** * Returns a string that may safely be used as the href of a * link to the route with the given name. */ function makeHref(routeName, params, query) { var path = makePath(routeName, params, query); if (URLStore.getLocation() === 'hash') return '#' + path; return path; } module.exports = makeHref; },{"../stores/URLStore":28,"./makePath":18}],18:[function(_dereq_,module,exports){ var invariant = _dereq_('react/lib/invariant'); var RouteStore = _dereq_('../stores/RouteStore'); var Path = _dereq_('./Path'); /** * Returns an absolute URL path created from the given route name, URL * parameters, and query values. */ function makePath(to, params, query) { var path; if (to.charAt(0) === '/') { path = Path.normalize(to); // Absolute path. } else { var route = RouteStore.getRouteByName(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); } module.exports = makePath; },{"../stores/RouteStore":27,"./Path":14,"react/lib/invariant":60}],19:[function(_dereq_,module,exports){ function mergeProperties(object, properties) { for (var property in properties) { if (properties.hasOwnProperty(property)) object[property] = properties[property]; } return object; } module.exports = mergeProperties; },{}],20:[function(_dereq_,module,exports){ var URLStore = _dereq_('../stores/URLStore'); var makePath = _dereq_('./makePath'); /** * Transitions to the URL specified in the arguments by replacing * the current URL in the history stack. */ function replaceWith(to, params, query) { URLStore.replace(makePath(to, params, query)); } module.exports = replaceWith; },{"../stores/URLStore":28,"./makePath":18}],21:[function(_dereq_,module,exports){ var Promise = _dereq_('es6-promise').Promise; /** * Resolves all values in asyncState and calls the setState * function with new state as they resolve. Returns a promise * that resolves after all values are resolved. */ function resolveAsyncState(asyncState, setState) { if (asyncState == null) return Promise.resolve(); var keys = Object.keys(asyncState); return Promise.all( keys.map(function (key) { return Promise.resolve(asyncState[key]).then(function (value) { var newState = {}; newState[key] = value; setState(newState); }); }) ); } module.exports = resolveAsyncState; },{"es6-promise":32}],22:[function(_dereq_,module,exports){ var URLStore = _dereq_('../stores/URLStore'); var makePath = _dereq_('./makePath'); /** * Transitions to the URL specified in the arguments by pushing * a new URL onto the history stack. */ function transitionTo(to, params, query) { URLStore.push(makePath(to, params, query)); } module.exports = transitionTo; },{"../stores/URLStore":28,"./makePath":18}],23:[function(_dereq_,module,exports){ function withoutProperties(object, properties) { var result = {}; for (var property in object) { if (object.hasOwnProperty(property) && !properties[property]) result[property] = object[property]; } return result; } module.exports = withoutProperties; },{}],24:[function(_dereq_,module,exports){ var ActiveStore = _dereq_('../stores/ActiveStore'); /** * A mixin for components that need to know about the routes, params, * and query that are currently active. Components that use it get two * things: * * 1. An `isActive` static method they can use to check if a route, * params, and query are active. * 2. An `updateActiveState` instance method that is called when the * active state changes. * * Example: * * var Tab = React.createClass({ * * mixins: [ Router.ActiveState ], * * getInitialState: function () { * return { * isActive: false * }; * }, * * updateActiveState: function () { * this.setState({ * isActive: Tab.isActive(routeName, params, query) * }) * } * * }); */ var ActiveState = { statics: { /** * Returns true if the route with the given name, URL parameters, and query * are all currently active. */ isActive: ActiveStore.isActive }, componentWillMount: function () { ActiveStore.addChangeListener(this.handleActiveStateChange); }, componentDidMount: function () { if (this.updateActiveState) this.updateActiveState(); }, componentWillUnmount: function () { ActiveStore.removeChangeListener(this.handleActiveStateChange); }, handleActiveStateChange: function () { if (this.isMounted() && this.updateActiveState) this.updateActiveState(); } }; module.exports = ActiveState; },{"../stores/ActiveStore":26}],25:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var resolveAsyncState = _dereq_('../helpers/resolveAsyncState'); /** * A mixin for route handler component classes that fetch at least * part of their state asynchronously. Classes that use it should * declare a static `getInitialAsyncState` method that fetches state * for a component after it mounts. This function is given three * arguments: 1) the current route params, 2) the current query and * 3) a function that can be used to set state as it is received. * * Much like the familiar `getInitialState` method, `getInitialAsyncState` * should return a hash of key/value pairs to use in the component's * state. The difference is that the values may be promises. As these * values resolve, the component's state is updated. You should only * ever need to use the setState function for doing things like * streaming data and/or updating progress. * * Example: * * var User = React.createClass({ * * statics: { * * getInitialAsyncState: function (params, query, setState) { * // Return a hash with keys named after the state variables * // you want to set, as you normally do in getInitialState, * // except the values may be immediate values or promises. * // The state is automatically updated as promises resolve. * return { * user: getUserByID(params.userID) // may be a promise * }; * * // Or, use the setState function to stream data! * var buffer = ''; * * return { * * // Same as above, the stream state variable is set to the * // value returned by this promise when it resolves. * stream: getStreamingData(params.userID, function (chunk) { * buffer += chunk; * * // Notify of progress. * setState({ * streamBuffer: buffer * }); * }) * * }; * } * * }, * * getInitialState: function () { * return { * user: null, // Receives a value when getUserByID resolves. * stream: null, // Receives a value when getStreamingData resolves. * streamBuffer: '' // Used to track data as it loads. * }; * }, * * render: function () { * if (!this.state.user) * return <LoadingUser/>; * * return ( * <div> * <p>Welcome {this.state.user.name}!</p> * <p>So far, you've received {this.state.streamBuffer.length} data!</p> * </div> * ); * } * * }); * * When testing, use the `initialAsyncState` prop to simulate asynchronous * data fetching. When this prop is present, no attempt is made to retrieve * additional state via `getInitialAsyncState`. */ var AsyncState = { propTypes: { initialAsyncState: React.PropTypes.object }, getInitialState: function () { return this.props.initialAsyncState || null; }, updateAsyncState: function (state) { if (this.isMounted()) this.setState(state); }, componentDidMount: function () { if (this.props.initialAsyncState || !this.constructor.getInitialAsyncState) return; resolveAsyncState( this.constructor.getInitialAsyncState(this.props.params, this.props.query, this.updateAsyncState), this.updateAsyncState ); } }; module.exports = AsyncState; },{"../helpers/resolveAsyncState":21}],26:[function(_dereq_,module,exports){ var _activeRoutes = []; var _activeParams = {}; var _activeQuery = {}; function routeIsActive(routeName) { return _activeRoutes.some(function (route) { return route.props.name === routeName; }); } function paramsAreActive(params) { for (var property in params) { if (_activeParams[property] !== String(params[property])) return false; } return true; } function queryIsActive(query) { for (var property in query) { if (_activeQuery[property] !== String(query[property])) return false; } return true; } var EventEmitter = _dereq_('event-emitter'); var _events = EventEmitter(); function notifyChange() { _events.emit('change'); } /** * The ActiveStore keeps track of which routes, URL and query parameters are * currently active on a page. <Link>s subscribe to the ActiveStore to know * whether or not they are active. */ var ActiveStore = { /** * Adds a listener that will be called when this store changes. */ addChangeListener: function (listener) { _events.on('change', listener); }, /** * Removes the given change listener. */ removeChangeListener: function (listener) { _events.off('change', listener); }, /** * Updates the currently active state and notifies all listeners. * This is automatically called by routes as they become active. */ updateState: function (state) { state = state || {}; _activeRoutes = state.activeRoutes || []; _activeParams = state.activeParams || {}; _activeQuery = state.activeQuery || {}; notifyChange(); }, /** * 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(routeName) && paramsAreActive(params); if (query) return isActive && queryIsActive(query); return isActive; } }; module.exports = ActiveStore; },{"event-emitter":42}],27:[function(_dereq_,module,exports){ var React = (typeof window !== "undefined" ? window.React : typeof global !== "undefined" ? global.React : null); var invariant = _dereq_('react/lib/invariant'); var warning = _dereq_('react/lib/warning'); var Path = _dereq_('../helpers/Path'); var _namedRoutes = {}; /** * The RouteStore contains a directory of all <Route>s in the system. It is * used primarily for looking up routes by name so that <Link>s can use a * route name in the "to" prop and users can use route names in `Router.transitionTo` * and other high-level utility methods. */ var RouteStore = { /** * Removes all references to <Route>s from the store. Should only ever * really be used in tests to clear the store between test runs. */ unregisterAllRoutes: function () { _namedRoutes = {}; }, /** * Removes the reference to the given <Route> and all of its children * from the store. */ unregisterRoute: function (route) { if (route.props.name) delete _namedRoutes[route.props.name]; React.Children.forEach(route.props.children, function (child) { RouteStore.unregisterRoute(child); }); }, /** * Registers a <Route> and all of its children with the store. Also, * does some normalization and validation on route props. */ registerRoute: function (route, _parentRoute) { // Make sure the <Route>'s path begins with a slash. Default to its name. // We can't do this in getDefaultProps because it may not be called on // <Route>s that are never actually mounted. if (route.props.path || route.props.name) { route.props.path = Path.normalize(route.props.path || route.props.name); } else { route.props.path = '/'; } // Make sure the <Route> has a valid React component for a handler. invariant( React.isValidClass(route.props.handler), 'The handler for Route "' + (route.props.name || route.props.path) + '" ' + 'must be a valid React component' ); // Make sure the <Route> has all params that its parent needs. if (_parentRoute) { var paramNames = Path.extractParamNames(route.props.path); Path.extractParamNames(_parentRoute.props.path).forEach(function (paramName) { invariant( paramNames.indexOf(paramName) !== -1, 'The nested route path "' + route.props.path + '" is missing the "' + paramName + '" ' + 'parameter of its parent path "' + _parentRoute.props.path + '"' ); }); } // Make sure the <Route> can be looked up by <Link>s. if (route.props.name) { var existingRoute = _namedRoutes[route.props.name]; invariant( !existingRoute || route === existingRoute, 'You cannot use the name "' + route.props.name + '" for more than one route' ); _namedRoutes[route.props.name] = route; } React.Children.forEach(route.props.children, function (child) { RouteStore.registerRoute(child, route); }); }, /** * Returns the Route object with the given name, if one exists. */ getRouteByName: function (routeName) { return _namedRoutes[routeName] || null; } }; module.exports = RouteStore; },{"../helpers/Path":14,"react/lib/invariant":60,"react/lib/warning":61}],28:[function(_dereq_,module,exports){ var ExecutionEnvironment = _dereq_('react/lib/ExecutionEnvironment'); var invariant = _dereq_('react/lib/invariant'); var warning = _dereq_('react/lib/warning'); var _location; var _currentPath = '/'; var _lastPath = null; function getWindowChangeEvent(location) { if (location === 'history') return 'popstate'; return window.addEventListener ? 'hashchange' : 'onhashchange'; } function getWindowPath() { return window.location.pathname + window.location.search; } var EventEmitter = _dereq_('event-emitter'); var _events = EventEmitter(); function notifyChange() { _events.emit('change'); } /** * The URLStore keeps track of the current URL. In DOM environments, it may be * attached to window.location to automatically sync with the URL in a browser's * location bar. <Route>s subscribe to the URLStore to know when the URL changes. */ var URLStore = { /** * Adds a listener that will be called when this store changes. */ addChangeListener: function (listener) { _events.on('change', listener); }, /** * Removes the given change listener. */ removeChangeListener: function (listener) { _events.off('change', listener); }, /** * Returns the type of navigation that is currently being used. */ getLocation: function () { return _location || 'hash'; }, /** * Returns the value of the current URL path. */ getCurrentPath: function () { if (_location === 'history' || _location === 'disabledHistory') return getWindowPath(); if (_location === 'hash') return window.location.hash.substr(1); return _currentPath; }, /** * Pushes the given path onto the browser navigation stack. */ push: function (path) { if (path === this.getCurrentPath()) return; if (_location === 'disabledHistory') return window.location = path; if (_location === 'history') { window.history.pushState({ path: path }, '', path); notifyChange(); } else if (_location === 'hash') { window.location.hash = path; } else { _lastPath = _currentPath; _currentPath = path; notifyChange(); } }, /** * Replaces the current URL path with the given path without adding an entry * to the browser's history. */ replace: function (path) { if (_location === 'disabledHistory') { window.location.replace(path); } else if (_location === 'history') { window.history.replaceState({ path: path }, '', path); notifyChange(); } else if (_location === 'hash') { window.location.replace(getWindowPath() + '#' + path); } else { _currentPath = path; notifyChange(); } }, /** * Reverts the URL to whatever it was before the last update. */ back: function () { if (_location != null) { window.history.back(); } else { invariant( _lastPath, 'You cannot make the URL store go back more than once when it does not use the DOM' ); _currentPath = _lastPath; _lastPath = null; notifyChange(); } }, /** * Returns true if the URL store has already been setup. */ isSetup: function () { return _location != null; }, /** * Sets up the URL store to get the value of the current path from window.location * as it changes. The location argument may be either "hash" or "history". */ setup: function (location) { invariant( ExecutionEnvironment.canUseDOM, 'You cannot setup the URL store in an environment with no DOM' ); if (_location != null) { warning( _location === location, 'The URL store was already setup using ' + _location + ' location. ' + 'You cannot use ' + location + ' location on the same page' ); return; // Don't setup twice. } if (location === 'history' && !supportsHistory()) { _location = 'disabledHistory'; return; } var changeEvent = getWindowChangeEvent(location); invariant( changeEvent || location === 'disabledHistory', 'The URL store location "' + location + '" is not valid. ' + 'It must be either "hash" or "history"' ); _location = location; if (location === 'hash' && window.location.hash === '') URLStore.replace('/'); if (window.addEventListener) { window.addEventListener(changeEvent, notifyChange, false); } else { window.attachEvent(changeEvent, notifyChange); } notifyChange(); }, /** * Stops listening for changes to window.location. */ teardown: function () { if (_location == null) return; var changeEvent = getWindowChangeEvent(_location); if (window.removeEventListener) { window.removeEventListener(changeEvent, notifyChange, false); } else { window.detachEvent(changeEvent, notifyChange); } _location = null; _currentPath = '/'; } }; function supportsHistory() { /*! taken from modernizr * https://github.com/Modernizr/Modernizr/blob/master/LICENSE * https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js */ var ua = navigator.userAgent; if ((ua.indexOf('Android 2.') !== -1 || (ua.indexOf('Android 4.0') !== -1)) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1) { return false; } return (window.history && 'pushState' in window.history); } module.exports = URLStore; },{"event-emitter":42,"react/lib/ExecutionEnvironment":57,"react/lib/invariant":60,"react/lib/warning":61}],29:[function(_dereq_,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. 'use strict'; // If obj.hasOwnProperty has been overridden, then calling // obj.hasOwnProperty(prop) will break. // See: https://github.com/joyent/node/issues/1707 function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } module.exports = function(qs, sep, eq, options) { sep = sep || '&'; eq = eq || '='; var obj = {}; if (typeof qs !== 'string' || qs.length === 0) { return obj; } var regexp = /\+/g; qs = qs.split(sep); var maxKeys = 1000; if (options && typeof options.maxKeys === 'number') { maxKeys = options.maxKeys; } var len = qs.length; // maxKeys <= 0 means that we should not limit keys count if (maxKeys > 0 && len > maxKeys) { len = maxKeys; } for (var i = 0; i < len; ++i) { var x = qs[i].replace(regexp, '%20'), idx = x.indexOf(eq), kstr, vstr, k, v; if (idx >= 0) { kstr = x.substr(0, idx); vstr = x.substr(idx + 1); } else { kstr = x; vstr = ''; } k = decodeURIComponent(kstr); v = decodeURIComponent(vstr); if (!hasOwnProperty(obj, k)) { obj[k] = v; } else if (isArray(obj[k])) { obj[k].push(v); } else { obj[k] = [obj[k], v]; } } return obj; }; var isArray = Array.isArray || function (xs) { return Object.prototype.toString.call(xs) =