Skip to content

Commit

Permalink
Merge pull request #1891 from banderson/remove-selected
Browse files Browse the repository at this point in the history
removeSelected prop (round 2), for optionally keeping selected values in dropdown
  • Loading branch information
JedWatson authored Oct 25, 2017
2 parents 5a273e1 + 41378ff commit 381ebc1
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ The built-in Options renderer also support custom classNames, just add a `classN

You can enable multi-value selection by setting `multi={true}`. In this mode:

* Selected options will be removed from the dropdown menu
* Selected options will be removed from the dropdown menu by default. If you want them to remain in the list, set `removeSelected={false}`
* The selected values are submitted in multiple `<input type="hidden">` fields, use `joinValues` to submit joined values in a single field instead
* The values of the selected items are joined using the `delimiter` prop to create the input value when `joinValues` is true
* A simple value, if provided, will be split using the `delimiter` prop
Expand Down
8 changes: 7 additions & 1 deletion examples/src/components/Multiselect.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var MultiSelectField = createClass({
},
getInitialState () {
return {
removeSelected: true,
disabled: false,
crazy: false,
stayOpen: false,
Expand Down Expand Up @@ -57,14 +58,19 @@ var MultiSelectField = createClass({
onChange={this.handleSelectChange}
options={options}
placeholder="Select your favourite(s)"
removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
/>

<div className="checkbox-list">
<label className="checkbox">
<input type="checkbox" className="checkbox-control" name="disabled" checked={disabled} onChange={this.toggleCheckbox} />
<input type="checkbox" className="checkbox-control" checked={this.state.removeSelected} onChange={this.toggleRemove} />
<span className="checkbox-label">Remove selected options</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.disabled} onChange={this.toggleDisabled} />
<span className="checkbox-label">Disable the control</span>
</label>
<label className="checkbox">
Expand Down
13 changes: 10 additions & 3 deletions src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,12 @@ class Select extends React.Component {
inputValue: this.handleInputValueChange(updatedValue),
isOpen: !this.props.closeOnSelect,
}, () => {
this.addValue(value);
var valueArray = this.getValueArray(this.props.value);
if (valueArray.some(i => i[this.props.valueKey] === value[this.props.valueKey])) {
this.removeValue(value);
} else {
this.addValue(value);
}
});
} else {
this.setState({
Expand Down Expand Up @@ -572,7 +577,7 @@ class Select extends React.Component {

removeValue (value) {
var valueArray = this.getValueArray(this.props.value);
this.setValue(valueArray.filter(i => i !== value));
this.setValue(valueArray.filter(i => i[this.props.valueKey] !== value[this.props.valueKey]));
this.focus();
}

Expand Down Expand Up @@ -999,7 +1004,7 @@ class Select extends React.Component {

render () {
let valueArray = this.getValueArray(this.props.value);
let options = this._visibleOptions = this.filterOptions(this.props.multi ? this.getValueArray(this.props.value) : null);
let options = this._visibleOptions = this.filterOptions(this.props.multi && this.props.removeSelected ? valueArray : null);
let isOpen = this.state.isOpen;
if (this.props.multi && !options.length && valueArray.length && !this.state.inputValue) isOpen = false;
const focusedOptionIndex = this.getFocusableOptionIndex(valueArray[0]);
Expand Down Expand Up @@ -1128,6 +1133,7 @@ Select.propTypes = {
options: PropTypes.array, // array of options
pageSize: PropTypes.number, // number of entries to page when using page up/down keys
placeholder: stringOrNode, // field placeholder, displayed when there's no value
removeSelected: PropTypes.bool, // whether the selected option is removed from the dropdown on multi selects
required: PropTypes.bool, // applies HTML5 required attribute when needed
resetValue: PropTypes.any, // value to use when you clear the control
rtl: PropTypes.bool, // set to true in order to use react-select in right-to-left direction
Expand Down Expand Up @@ -1179,6 +1185,7 @@ Select.defaultProps = {
optionComponent: Option,
pageSize: 5,
placeholder: 'Select...',
removeSelected: true,
required: false,
rtl: false,
scrollMenuIntoView: true,
Expand Down
2 changes: 1 addition & 1 deletion src/utils/defaultMenuRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function menuRenderer ({
let Option = optionComponent;

return options.map((option, i) => {
let isSelected = valueArray && valueArray.indexOf(option) > -1;
let isSelected = valueArray && valueArray.some(x => x[valueKey] == option[valueKey]);
let isFocused = option === focusedOption;
let optionClass = classNames(optionClassName, {
'Select-option': true,
Expand Down
104 changes: 104 additions & 0 deletions test/Select-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2041,6 +2041,110 @@ describe('Select', () => {

});

describe('with removeSelected=false', () => {
beforeEach(() => {
options = [
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
{ value: 'four', label: 'Four' }
];

// Render an instance of the component
wrapper = createControlWithWrapper({
value: '',
options: options,
multi: true,
closeOnSelect: false,
removeSelected: false
}, {
wireUpOnChangeToValue: true
});

// We need a hack here.
// JSDOM (at least v3.x) doesn't appear to support div's with tabindex
// This just hacks that we are focused
// This is (obviously) implementation dependent, and may need to change
instance.setState({
isFocused: true
});
});

it('does not remove the selected options from the menu', () => {

clickArrowToOpen();

var items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');

// Click the option "Two" to select it
expect(items[1], 'to have text', 'Two');
TestUtils.Simulate.mouseDown(items[1]);
expect(onChange, 'was called times', 1);

// Now get the list again
items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
expect(items[0], 'to have text', 'One');
expect(items[1], 'to have text', 'Two');
expect(items[2], 'to have text', 'Three');
expect(items[3], 'to have text', 'Four');
expect(items, 'to have length', 4);

// Click first item, 'One'
TestUtils.Simulate.mouseDown(items[0]);
expect(onChange, 'was called times', 2);

items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
expect(items[0], 'to have text', 'One');
expect(items[1], 'to have text', 'Two');
expect(items[2], 'to have text', 'Three');
expect(items[3], 'to have text', 'Four');
expect(items, 'to have length', 4);

// Click last item, 'Four'
TestUtils.Simulate.mouseDown(items[3]);
expect(onChange, 'was called times', 3);

items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
expect(items[0], 'to have text', 'One');
expect(items[1], 'to have text', 'Two');
expect(items[2], 'to have text', 'Three');
expect(items[3], 'to have text', 'Four');
expect(items, 'to have length', 4);

expect(onChange.args, 'to equal', [
[[{ value: 'two', label: 'Two' }]],
[[{ value: 'two', label: 'Two' }, { value: 'one', label: 'One' }]],
[
[
{ value: 'two', label: 'Two' },
{ value: 'one', label: 'One' },
{ value: 'four', label: 'Four' },
],
],
]);
});

it('removes a selected value if chosen again', () => {

clickArrowToOpen();

var items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');

// Click the option "Two" to select it
TestUtils.Simulate.mouseDown(items[1]);
expect(onChange, 'was called times', 1);

// Click the option "Two" again to deselect it
TestUtils.Simulate.mouseDown(items[1]);
expect(onChange, 'was called times', 2);

expect(onChange.args, 'to equal', [
[[{ value: 'two', label: 'Two' }]],
[[]],
]);
});
});

describe('with props', () => {

describe('className', () => {
Expand Down

0 comments on commit 381ebc1

Please sign in to comment.