react-router
Version:
A complete routing library for React.js
1,743 lines (1,425 loc) • 84.7 kB
JavaScript
!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) =