Skip to content
This repository has been archived by the owner on Jul 15, 2019. It is now read-only.

Commit

Permalink
navlink performance improvement, remove handleRoute and provide creat…
Browse files Browse the repository at this point in the history
…eNavLinkComponent for users to customize it
  • Loading branch information
kaesonho committed Jul 28, 2015
1 parent e62aea1 commit b61bf31
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 129 deletions.
25 changes: 25 additions & 0 deletions docs/api/createNavLinkComponent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# createNavLinkComponent

`createNavLinkComponent` is a function for you to create a NavLink component, you are able to pass options to overwrite attributes to generate the NavLink. e.g., custom mixin or click handler.

## Parameters

| Param Name | Param Type | Description |
|-----------|-----------|-------------|
| overwriteSpec | Object | the spec object taken to overwrite the default spec when we create NavLink using React.createClass |


## Example Usage

```js
var createNavLinkComponent = require('fluxible-router').createNavLinkComponent;

module.exports = createNavLinkComponent({
displayName: 'CustomNavLink',
mixins: [someMixin],
clickHandler: function (e) {
// custom click handler
this.dispatchNavAction(e);
}
});
```
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
'use strict';

module.exports = {
createNavLinkComponent: require('./lib/createNavLinkComponent'),
handleHistory: require('./lib/handleHistory'),
handleRoute: require('./lib/handleRoute'),
History: require('./lib/History'),
Expand Down
129 changes: 2 additions & 127 deletions lib/NavLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,131 +5,6 @@
/*global window */
'use strict';

var React = require('react');
var navigateAction = require('./navigateAction');
var debug = require('debug')('NavLink');
var objectAssign = require('object-assign');
var handleRoute = require('./handleRoute');
var Immutable = require('immutable');
var createNavLinkComponent = require('./createNavLinkComponent');

function isLeftClickEvent (e) {
return e.button === 0;
}

function isModifiedEvent (e) {
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
}

var NavLink = React.createClass({
displayName: 'NavLink',
contextTypes: {
executeAction: React.PropTypes.func
},
propTypes: {
currentRoute: React.PropTypes.object,
currentNavigate: React.PropTypes.object,
href: React.PropTypes.string,
isActive: React.PropTypes.func,
makePath: React.PropTypes.func,
stopPropagation: React.PropTypes.bool,
routeName: React.PropTypes.string,
navParams: React.PropTypes.object,
followLink: React.PropTypes.bool,
preserveScrollPosition: React.PropTypes.bool,
replaceState: React.PropTypes.bool
},
_getHrefFromProps: function (props) {
var href = props.href;
var routeName = props.routeName;
if (!href && routeName) {
href = this.props.makePath(routeName, props.navParams);
}
if (!href) {
throw new Error('NavLink created without href or unresolvable routeName \'' + routeName + '\'');
}
return href;
},
dispatchNavAction: function (e) {
var navType = this.props.replaceState ? 'replacestate' : 'click';
debug('dispatchNavAction: action=NAVIGATE', this.props.href, this.props.followLink, this.props.navParams);

if (this.props.followLink) {
return;
}

if (isModifiedEvent(e) || !isLeftClickEvent(e)) {
// this is a click with a modifier or not a left-click
// let browser handle it natively
return;
}

var href = this._getHrefFromProps(this.props);

if (href[0] === '#') {
// this is a hash link url for page's internal links.
// Do not trigger navigate action. Let browser handle it natively.
return;
}

if (href[0] !== '/') {
// this is not a relative url. check for external urls.
var location = window.location;
var origin = location.origin || (location.protocol + '//' + location.host);

if (href.indexOf(origin) !== 0) {
// this is an external url, do not trigger navigate action.
// let browser handle it natively.
return;
}

href = href.substring(origin.length) || '/';
}

e.preventDefault();
if (this.props.stopPropagation) {
e.stopPropagation();
}

var context = this.props.context || this.context;
var onBeforeUnloadText = typeof window.onbeforeunload === 'function' ? window.onbeforeunload() : '';
var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true;

if (confirmResult) {
// Removes the window.onbeforeunload method so that the next page will not be affected
window.onbeforeunload = null;

context.executeAction(navigateAction, {
type: navType,
url: href,
preserveScrollPosition: this.props.preserveScrollPosition,
params: this.props.navParams
});
}
},
render: function() {
var href = this._getHrefFromProps(this.props);
var isActive = this.props.isActive(href);

var className = this.props.className;
var style = this.props.style;
if (isActive) {
className = className ? (className + ' ') : '';
className += this.props.activeClass || 'active';
style = objectAssign({}, style, this.props.activeStyle);
}

return React.createElement(
'a',
objectAssign({}, {
onClick: this.dispatchNavAction
}, this.props, {
href: href,
className: className,
style: style
}),
this.props.children
);
}
});

module.exports = handleRoute(NavLink);
module.exports = createNavLinkComponent();
174 changes: 174 additions & 0 deletions lib/createNavLinkComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Copyright 2015, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
/*global window */
'use strict';
var React = require('react');
var RouteStore = require('./RouteStore');
var debug = require('debug')('NavLink');
var navigateAction = require('./navigateAction');
var objectAssign = require('object-assign');

function isLeftClickEvent (e) {
return e.button === 0;
}

function isModifiedEvent (e) {
return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
}

/**
* create NavLink component with custom options
* @param {Object} overwriteSpec spec to overwrite the default spec to create NavLink
* @returns {React.Component} NavLink component
*/
module.exports = function createNavLinkComponent (overwriteSpec) {
var NavLink = React.createClass(objectAssign({}, {
displayName: 'NavLink',
contextTypes: {
executeAction: React.PropTypes.func,
getStore: React.PropTypes.func
},
propTypes: {
href: React.PropTypes.string,
stopPropagation: React.PropTypes.bool,
routeName: React.PropTypes.string,
navParams: React.PropTypes.object,
followLink: React.PropTypes.bool,
preserveScrollPosition: React.PropTypes.bool,
replaceState: React.PropTypes.bool
},
getInitialState: function () {
return this._getState(this.props);
},
componentDidMount: function () {
var routeStore = this.context.getStore(RouteStore);
routeStore.addChangeListener(this._onRouteStoreChange);
},
componentWillUnmount: function () {
var routeStore = this.context.getStore(RouteStore);
routeStore.removeChangeListener(this._onRouteStoreChange);
},
shouldComponentUpdate: function (nextProps, nextState) {
if (this.state.isActive !== nextState.isActive || this.receivedNewProps) {
return true;
}
return false;
},
componentWillReceiveProps: function (nextProps) {
this.receivedNewProps = true;
this.setState(this._getState(nextProps));
},
_onRouteStoreChange: function () {
if (this.isMounted()) {
this.setState(this._getState(this.props));
}
},
_getState: function (props) {
var routeStore = this.context.getStore(RouteStore);
var href = this._getHrefFromProps(props);
var className = props.className;
var style = props.style;
var isActive = routeStore.isActive(href);
if (isActive) {
className = className ? (className + ' ') : '';
className += props.activeClass || 'active';
style = objectAssign({}, style, props.activeStyle);
}
return {
href: href,
isActive: isActive,
className: className,
style: style
};
},
_getHrefFromProps: function (props) {
var href = props.href;
var routeName = props.routeName;
var routeStore = this.context.getStore(RouteStore);
if (!href && routeName) {
href = routeStore.makePath(routeName, props.navParams);
}
if (!href) {
throw new Error('NavLink created without href or unresolvable routeName \'' + routeName + '\'');
}
return href;
},
dispatchNavAction: function (e) {
var navType = this.props.replaceState ? 'replacestate' : 'click';
debug('dispatchNavAction: action=NAVIGATE', this.props.href, this.props.followLink, this.props.navParams);

if (this.props.followLink) {
return;
}

if (isModifiedEvent(e) || !isLeftClickEvent(e)) {
// this is a click with a modifier or not a left-click
// let browser handle it natively
return;
}

var href = this._getHrefFromProps(this.props);

if (href[0] === '#') {
// this is a hash link url for page's internal links.
// Do not trigger navigate action. Let browser handle it natively.
return;
}

if (href[0] !== '/') {
// this is not a relative url. check for external urls.
var location = window.location;
var origin = location.origin || (location.protocol + '//' + location.host);

if (href.indexOf(origin) !== 0) {
// this is an external url, do not trigger navigate action.
// let browser handle it natively.
return;
}

href = href.substring(origin.length) || '/';
}

e.preventDefault();
if (this.props.stopPropagation) {
e.stopPropagation();
}

var context = this.props.context || this.context;
var onBeforeUnloadText = typeof window.onbeforeunload === 'function' ? window.onbeforeunload() : '';
var confirmResult = onBeforeUnloadText ? window.confirm(onBeforeUnloadText) : true;

if (confirmResult) {
// Removes the window.onbeforeunload method so that the next page will not be affected
window.onbeforeunload = null;

context.executeAction(navigateAction, {
type: navType,
url: href,
preserveScrollPosition: this.props.preserveScrollPosition,
params: this.props.navParams
});
}
},
clickHandler: function (e) {
this.dispatchNavAction(e);
},
render: function () {
this.receivedNewProps = false;
return React.createElement(
'a',
objectAssign({}, {
onClick: this.clickHandler
}, this.props, {
href: this.state.href,
className: this.state.className,
style: this.state.style
}),
this.props.children
);
}
}, overwriteSpec));
return NavLink;
};
4 changes: 2 additions & 2 deletions tests/unit/lib/NavLink-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,14 @@ describe('NavLink', function () {
setTimeout(function () {
expect(link.getDOMNode().getAttribute('href')).to.equal('/foo');
expect(link.getDOMNode().textContent).to.equal('bar');
expect(!link.getDOMNode().getAttribute('class'));
expect(!link.getDOMNode().getAttribute('class')).to.equal(true);
done();
}, 50);
});
});

describe('componentWillUnmount', function () {
it('should update active state', function () {
it('should remove the change listener', function () {
var div = document.createElement('div');
React.render(
<MockAppComponent context={mockContext}>
Expand Down

0 comments on commit b61bf31

Please sign in to comment.