react-router
Version:
A complete routing library for React.js
1,849 lines (1,483 loc) • 126 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){
/**
* 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