Skip to content

Commit 2ffedd4

Browse files
committed
Merge pull request #382 from bhstahl/disabled-clicks
Completely ignore clicks on disabled items
2 parents 2b6562d + 0bb61cd commit 2ffedd4

File tree

4 files changed

+147
-1
lines changed

4 files changed

+147
-1
lines changed

examples/src/app.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import SelectedValuesField from './components/SelectedValuesField';
1010
import StatesField from './components/StatesField';
1111
import UsersField from './components/UsersField';
1212
import ValuesAsNumbersField from './components/ValuesAsNumbersField';
13+
import DisabledUpsellOptions from './components/DisabledUpsellOptions';
1314

1415
var FLAVOURS = [
1516
{ label: 'Chocolate', value: 'chocolate' },
@@ -30,9 +31,11 @@ React.render(
3031
<StatesField label="States" searchable />
3132
<UsersField label="Users (custom options/value)" hint="This example uses Gravatar to render user's image besides the value and the options" />
3233
<ValuesAsNumbersField label="Values as numbers" />
34+
3335
<MultiSelectField label="Multiselect"/>
3436
<SelectedValuesField label="Clickable labels (labels as links)" options={FLAVOURS} hint="Open the console to see click behaviour (data/event)" />
3537
<SelectedValuesField label="Disabled option" options={FLAVOURS_WITH_DISABLED_OPTION} hint="You savage! Caramel is the best..." />
38+
<DisabledUpsellOptions label="Disable option with an upsell link"/>
3639
<SelectedValuesField label="Option Creation (tags mode)" options={FLAVOURS} allowCreate hint="Enter a value that's not in the list, then hit enter" />
3740
<CustomRenderField label="Custom render options/values" />
3841
<CustomRenderField label="Custom render options/values (multi)" multi delimiter="," />
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import Select from 'react-select';
3+
4+
function logChange() {
5+
console.log.apply(console, [].concat(['Select value changed:'], Array.prototype.slice.apply(arguments)));
6+
}
7+
8+
var DisabledUpsellOptions = React.createClass({
9+
displayName: 'DisabledUpsellOptions',
10+
propTypes: {
11+
label: React.PropTypes.string,
12+
},
13+
onLabelClick: function (data, event) {
14+
console.log(data, event);
15+
},
16+
renderLink: function() {
17+
return <a style={{ marginLeft: 5 }} href="/upgrade" target="_blank">Upgrade here!</a>;
18+
},
19+
renderOption: function(option) {
20+
return <span>{option.label} {option.link} </span>;
21+
},
22+
render: function() {
23+
var ops = [
24+
{ label: 'Basic customer support', value: 'basic' },
25+
{ label: 'Premium customer support', value: 'premium' },
26+
{ label: 'Pro customer support', value: 'pro', disabled: true, link: this.renderLink() },
27+
];
28+
return (
29+
<div className="section">
30+
<h3 className="section-heading">{this.props.label}</h3>
31+
<Select
32+
onOptionLabelClick={this.onLabelClick}
33+
placeholder="Select your support level"
34+
options={ops}
35+
optionRenderer={this.renderOption}
36+
onChange={logChange} />
37+
</div>
38+
);
39+
}
40+
});
41+
module.exports = DisabledUpsellOptions;

src/Option.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,30 @@ var Option = React.createClass({
1212
renderFunc: React.PropTypes.func // method passed to ReactSelect component to render label text
1313
},
1414

15+
blockEvent: function(event) {
16+
event.preventDefault();
17+
if ((event.target.tagName !== 'A') || !('href' in event.target)) {
18+
return;
19+
}
20+
21+
if (event.target.target) {
22+
window.open(event.target.href);
23+
} else {
24+
window.location.href = event.target.href;
25+
}
26+
},
27+
1528
render: function() {
1629
var obj = this.props.option;
1730
var renderedLabel = this.props.renderFunc(obj);
1831
var optionClasses = classes(this.props.className, obj.className);
1932

2033
return obj.disabled ? (
21-
<div className={optionClasses}>{renderedLabel}</div>
34+
<div className={optionClasses}
35+
onMouseDown={this.blockEvent}
36+
onClick={this.blockEvent}>
37+
{renderedLabel}
38+
</div>
2239
) : (
2340
<div className={optionClasses}
2441
style={obj.style}

test/Select-test.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,15 @@ describe('Select', function() {
945945
});
946946
expect(options[1], 'to have text', 'Three');
947947
});
948+
949+
it('is does not close menu when disabled option is clicked', function () {
950+
951+
clickArrowToOpen();
952+
TestUtils.Simulate.mouseDown(React.findDOMNode(instance).querySelectorAll('.Select-option')[1]);
953+
954+
var options = React.findDOMNode(instance).querySelectorAll('.Select-option');
955+
expect(options.length, 'to equal', 3);
956+
});
948957
});
949958

950959
describe('with styled options', function () {
@@ -2460,6 +2469,82 @@ describe('Select', function() {
24602469
});
24612470
});
24622471

2472+
describe('optionRendererDisabled', function () {
2473+
2474+
var optionRenderer;
2475+
var renderLink = function(props) {
2476+
return <a {...props} >Upgrade here!</a>;
2477+
};
2478+
2479+
var links = [
2480+
{ href: '/link' },
2481+
{ href: '/link2', target: '_blank' }
2482+
];
2483+
2484+
var ops = [
2485+
{ label: 'Disabled', value: 'disabled', disabled: true, link: renderLink(links[0]) },
2486+
{ label: 'Disabled 2', value: 'disabled_2', disabled: true, link: renderLink(links[1]) },
2487+
{ label: 'Enabled', value: 'enabled' },
2488+
];
2489+
2490+
/**
2491+
* Since we don't have access to an actual Location object,
2492+
* this method will test a string (path) by the end of global.window.location.href
2493+
* @param {string} path Ending href path to check
2494+
* @return {Boolean} Whether the location is at the path
2495+
*/
2496+
var isNavigated = function(path) {
2497+
var window_location = global.window.location.href;
2498+
return window_location.indexOf(path, window_location.length - path.length) !== -1;
2499+
};
2500+
2501+
beforeEach(function () {
2502+
2503+
optionRenderer = function (option) {
2504+
return (
2505+
<span>{option.label} {option.link} </span>
2506+
);
2507+
};
2508+
2509+
optionRenderer = sinon.spy(optionRenderer);
2510+
2511+
instance = createControl({
2512+
options: ops,
2513+
optionRenderer: optionRenderer
2514+
});
2515+
});
2516+
2517+
it('disabled option link is still clickable', function () {
2518+
var selectArrow = React.findDOMNode(instance).querySelector('.Select-arrow');
2519+
TestUtils.Simulate.mouseDown(selectArrow);
2520+
var options = React.findDOMNode(instance).querySelectorAll('.Select-option');
2521+
var link = options[0].querySelector('a');
2522+
expect(link, 'to have attributes', {
2523+
href: links[0].href
2524+
});
2525+
2526+
expect(isNavigated(links[0].href), 'to be false');
2527+
TestUtils.Simulate.click(link);
2528+
expect(isNavigated(links[0].href), 'to be true');
2529+
});
2530+
2531+
it('disabled option link with target doesn\'t navigate the current window', function () {
2532+
var selectArrow = React.findDOMNode(instance).querySelector('.Select-arrow');
2533+
TestUtils.Simulate.mouseDown(selectArrow);
2534+
var options = React.findDOMNode(instance).querySelectorAll('.Select-option');
2535+
var link = options[1].querySelector('a');
2536+
expect(link, 'to have attributes', {
2537+
href: links[1].href,
2538+
target: '_blank'
2539+
});
2540+
2541+
expect(isNavigated(links[0].href), 'to be true');
2542+
TestUtils.Simulate.click(link);
2543+
expect(isNavigated(links[1].href), 'to be false');
2544+
});
2545+
2546+
});
2547+
24632548
describe('placeholder', function () {
24642549

24652550
beforeEach(function () {

0 commit comments

Comments
 (0)