Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

removeSelected prop (round 2), for optionally keeping selected values in dropdown #1891

Merged
merged 17 commits into from
Oct 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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