react-router
Version:
A complete routing library for React.js
148 lines (120 loc) • 3.7 kB
JavaScript
var React = require('react');
var ActiveState = require('../mixins/ActiveState');
var withoutProperties = require('../helpers/withoutProperties');
var transitionTo = require('../helpers/transitionTo');
var makeHref = require('../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;