Skip to content

Commit

Permalink
[changed] isActive is an instance method
Browse files Browse the repository at this point in the history
[removed] <Routes onActiveStateChange>

This commit removes ActiveStore (yay!). Instead, <Routes> components
now store their own active state and emit active state change events
to ActiveState descendants that are interested.
  • Loading branch information
mjackson authored and ryanflorence committed Sep 2, 2014
1 parent af1fb6e commit a4ce7c8
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 208 deletions.
4 changes: 2 additions & 2 deletions modules/components/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,13 @@ var Link = React.createClass({
var params = Link.getParams(nextProps);

this.setState({
isActive: Link.isActive(nextProps.to, params, nextProps.query)
isActive: this.isActive(nextProps.to, params, nextProps.query)
});
},

updateActiveState: function () {
this.setState({
isActive: Link.isActive(this.props.to, Link.getParams(this.props), this.props.query)
isActive: this.isActive(this.props.to, Link.getParams(this.props), this.props.query)
});
},

Expand Down
16 changes: 4 additions & 12 deletions modules/components/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var DefaultLocation = require('../locations/DefaultLocation');
var HashLocation = require('../locations/HashLocation');
var HistoryLocation = require('../locations/HistoryLocation');
var RefreshLocation = require('../locations/RefreshLocation');
var ActiveStore = require('../stores/ActiveStore');
var ActiveDelegate = require('../mixins/ActiveDelegate');
var PathStore = require('../stores/PathStore');
var RouteStore = require('../stores/RouteStore');

Expand Down Expand Up @@ -43,13 +43,6 @@ function defaultAbortedTransitionHandler(transition) {
}
}

/**
* The default handler for active state updates.
*/
function defaultActiveStateChangeHandler(state) {
ActiveStore.updateState(state);
}

/**
* The default handler for errors that were thrown asynchronously
* while transitioning. The default behavior is to re-throw the
Expand All @@ -74,9 +67,10 @@ var Routes = React.createClass({

displayName: 'Routes',

mixins: [ ActiveDelegate ],

propTypes: {
onAbortedTransition: React.PropTypes.func.isRequired,
onActiveStateChange: React.PropTypes.func.isRequired,
onTransitionError: React.PropTypes.func.isRequired,
preserveScrollPosition: React.PropTypes.bool,
location: function (props, propName, componentName) {
Expand All @@ -90,7 +84,6 @@ var Routes = React.createClass({
getDefaultProps: function () {
return {
onAbortedTransition: defaultAbortedTransitionHandler,
onActiveStateChange: defaultActiveStateChangeHandler,
onTransitionError: defaultTransitionErrorHandler,
preserveScrollPosition: false,
location: DefaultLocation
Expand Down Expand Up @@ -182,8 +175,7 @@ var Routes = React.createClass({
if (transition.isAborted) {
routes.props.onAbortedTransition(transition);
} else if (nextState) {
routes.setState(nextState);
routes.props.onActiveStateChange(nextState);
routes.setState(nextState, routes.emitChange);

// TODO: add functional test
var rootMatch = getRootMatch(nextState.matches);
Expand Down
65 changes: 65 additions & 0 deletions modules/mixins/ActiveDelegate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
var React = require('react');
var ChangeEmitter = require('./ChangeEmitter');

function routeIsActive(activeRoutes, routeName) {
return activeRoutes.some(function (route) {
return route.props.name === routeName;
});
}

function paramsAreActive(activeParams, params) {
for (var property in params) {
if (activeParams[property] !== String(params[property]))
return false;
}

return true;
}

function queryIsActive(activeQuery, query) {
for (var property in query) {
if (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 ActiveDelegate = {

mixins: [ ChangeEmitter ],

childContextTypes: {
activeDelegate: React.PropTypes.any.isRequired
},

getChildContext: function () {
return {
activeDelegate: this
};
},

/**
* Returns true if the route with the given name, URL parameters, and
* query are all currently active.
*/
isActive: function (routeName, params, query) {
var activeRoutes = this.state.activeRoutes || [];
var activeParams = this.state.activeParams || {};
var activeQuery = this.state.activeQuery || {};

var isActive = routeIsActive(activeRoutes, routeName) && paramsAreActive(activeParams, params);

if (query)
return isActive && queryIsActive(activeQuery, query);

return isActive;
}

};

module.exports = ActiveDelegate;
43 changes: 25 additions & 18 deletions modules/mixins/ActiveState.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
var ActiveStore = require('../stores/ActiveStore');
var React = require('react');
var ActiveDelegate = require('./ActiveDelegate');

/**
* 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
* 1. An `updateActiveState` method that is called when the
* active state changes.
* 2. An `isActive` method they can use to check if a route,
* params, and query are active.
*
*
* Example:
*
Expand All @@ -24,40 +26,45 @@ var ActiveStore = require('../stores/ActiveStore');
*
* updateActiveState: function () {
* this.setState({
* isActive: Tab.isActive(routeName, params, query)
* isActive: this.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

contextTypes: {
activeDelegate: React.PropTypes.any.isRequired
},

componentWillMount: function () {
ActiveStore.addChangeListener(this.handleActiveStateChange);
/**
* Returns this component's ActiveDelegate component.
*/
getActiveDelegate: function () {
return this.context.activeDelegate;
},

componentDidMount: function () {
if (this.updateActiveState)
this.updateActiveState();
this.getActiveDelegate().addChangeListener(this.handleActiveStateChange);
this.handleActiveStateChange();
},

componentWillUnmount: function () {
ActiveStore.removeChangeListener(this.handleActiveStateChange);
this.getActiveDelegate().removeChangeListener(this.handleActiveStateChange);
},

handleActiveStateChange: function () {
if (this.isMounted() && typeof this.updateActiveState === 'function')
this.updateActiveState();
},

/**
* Returns true if the route with the given name, URL parameters, and
* query are all currently active.
*/
isActive: function (routeName, params, query) {
return this.getActiveDelegate().isActive(routeName, params, query);
}

};
Expand Down
50 changes: 50 additions & 0 deletions modules/mixins/ChangeEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
var React = require('react');
var EventEmitter = require('events').EventEmitter;

var CHANGE_EVENT = 'change';

/**
* A mixin for components that emit change events. ActiveDelegate uses
* this mixin to notify descendant ActiveState components when the
* active state changes.
*/
var ChangeEmitter = {

propTypes: {
maxChangeListeners: React.PropTypes.number.isRequired
},

getDefaultProps: function () {
return {
maxChangeListeners: 0
};
},

componentWillMount: function () {
this._events = new EventEmitter;
this._events.setMaxListeners(this.props.maxChangeListeners);
},

componentWillReceiveProps: function (nextProps) {
this._events.setMaxListeners(nextProps.maxChangeListeners);
},

componentWillUnmount: function () {
this._events.removeAllListeners();
},

addChangeListener: function (listener) {
this._events.addListener(CHANGE_EVENT, listener);
},

removeChangeListener: function (listener) {
this._events.removeListener(CHANGE_EVENT, listener);
},

emitChange: function () {
this._events.emit(CHANGE_EVENT);
}

};

module.exports = ChangeEmitter;
84 changes: 0 additions & 84 deletions modules/stores/ActiveStore.js

This file was deleted.

0 comments on commit a4ce7c8

Please sign in to comment.