diff --git a/examples/src/app.js b/examples/src/app.js
index bfda0ce816..628f34b60f 100644
--- a/examples/src/app.js
+++ b/examples/src/app.js
@@ -12,6 +12,7 @@ import Multiselect from './components/Multiselect';
import NumericSelect from './components/NumericSelect';
import Virtualized from './components/Virtualized';
import States from './components/States';
+import AllowCreate from './components/AllowCreate';
ReactDOM.render(
@@ -26,6 +27,7 @@ ReactDOM.render(
{/*
*/}
+
,
document.getElementById('example')
);
diff --git a/examples/src/components/AllowCreate.js b/examples/src/components/AllowCreate.js
new file mode 100644
index 0000000000..4cab93a320
--- /dev/null
+++ b/examples/src/components/AllowCreate.js
@@ -0,0 +1,61 @@
+import React from 'react';
+import Select from 'react-select';
+
+const FLAVOURS = [
+ { label: 'Chocolate', value: 'chocolate' },
+ { label: 'Vanilla', value: 'vanilla' },
+ { label: 'Strawberry', value: 'strawberry' },
+ { label: 'Caramel', value: 'caramel' },
+ { label: 'Cookies and Cream', value: 'cookiescream' },
+ { label: 'Peppermint', value: 'peppermint' },
+];
+
+var AllowCreate = React.createClass({
+ displayName: 'AllowCreate',
+
+ propTypes: {
+ allowCreate: React.PropTypes.bool,
+ label: React.PropTypes.string,
+ },
+
+ getInitialState () {
+ return {
+ disabled: false,
+ crazy: false,
+ options: FLAVOURS,
+ value: [],
+ };
+ },
+
+ onLabelClick (data, event) {
+ console.log(data, event);
+ },
+
+ handleSelectChange (value){
+ this.setState({ value });
+ },
+
+ renderHint () {
+ return (
+ Create options in tag mode
+ );
+ },
+
+ render () {
+ return (
+
+
{this.props.label}
+
+ {this.renderHint()}
+
+ );
+ }
+});
+
+module.exports = AllowCreate;
diff --git a/src/Option.js b/src/Option.js
index 3ab7b481c2..1ea0ca2079 100644
--- a/src/Option.js
+++ b/src/Option.js
@@ -3,18 +3,20 @@ import classNames from 'classnames';
const Option = React.createClass({
propTypes: {
+ addLabelText: React.PropTypes.string, // text to display with value while creating new option
children: React.PropTypes.node,
- className: React.PropTypes.string, // className (based on mouse position)
+ className: React.PropTypes.string, // className (based on mouse position)
instancePrefix: React.PropTypes.string.isRequired, // unique prefix for the ids (used for aria)
- isDisabled: React.PropTypes.bool, // the option is disabled
- isFocused: React.PropTypes.bool, // the option is focused
- isSelected: React.PropTypes.bool, // the option is selected
- onFocus: React.PropTypes.func, // method to handle mouseEnter on option element
- onSelect: React.PropTypes.func, // method to handle click on option element
- onUnfocus: React.PropTypes.func, // method to handle mouseLeave on option element
- option: React.PropTypes.object.isRequired, // object that is base for that option
- optionIndex: React.PropTypes.number, // index of the option, used to generate unique ids for aria
+ isDisabled: React.PropTypes.bool, // the option is disabled
+ isFocused: React.PropTypes.bool, // the option is focused
+ isSelected: React.PropTypes.bool, // the option is selected
+ onFocus: React.PropTypes.func, // method to handle mouseEnter on option element
+ onSelect: React.PropTypes.func, // method to handle click on option element
+ onUnfocus: React.PropTypes.func, // method to handle mouseLeave on option element
+ option: React.PropTypes.object.isRequired, // object that is base for that option
+ optionIndex: React.PropTypes.number // index of the option, used to generate unique ids for aria
},
+
blockEvent (event) {
event.preventDefault();
event.stopPropagation();
@@ -68,7 +70,6 @@ const Option = React.createClass({
render () {
var { option, instancePrefix, optionIndex } = this.props;
var className = classNames(this.props.className, option.className);
-
return option.disabled ? (
- {this.props.children}
+ { option.create ? this.props.addLabelText.replace('{label}', option.label) : this.props.children }
);
}
diff --git a/src/Select.js b/src/Select.js
index acee0830dc..6e71664e01 100644
--- a/src/Select.js
+++ b/src/Select.js
@@ -749,9 +749,11 @@ const Select = React.createClass({
filterOptions (excludeOptions) {
var filterValue = this.state.inputValue;
+ var originalFilterValue = filterValue;
var options = this.props.options || [];
+ var filteredOptions = [];
if (typeof this.props.filterOptions === 'function') {
- return this.props.filterOptions.call(this, options, filterValue, excludeOptions);
+ filteredOptions = this.props.filterOptions.call(this, options, filterValue, excludeOptions);
} else if (this.props.filterOptions) {
if (this.props.ignoreAccents) {
filterValue = stripDiacritics(filterValue);
@@ -760,7 +762,7 @@ const Select = React.createClass({
filterValue = filterValue.toLowerCase();
}
if (excludeOptions) excludeOptions = excludeOptions.map(i => i[this.props.valueKey]);
- return options.filter(option => {
+ filteredOptions = options.filter(option => {
if (excludeOptions && excludeOptions.indexOf(option[this.props.valueKey]) > -1) return false;
if (this.props.filterOption) return this.props.filterOption.call(this, option, filterValue);
if (!filterValue) return true;
@@ -783,8 +785,26 @@ const Select = React.createClass({
);
});
} else {
- return options;
+ filteredOptions = options;
+ }
+ if (this.props.allowCreate && filterValue) {
+ let addNewOption = true;
+ //NOTE: only add the "Add" option if none of the options are an exact match
+ filteredOptions.map(option => {
+ if (option.label.toLowerCase() === filterValue || option.value.toLowerCase() === filterValue) {
+ addNewOption = false;
+ }
+ });
+ if (addNewOption) {
+ let newOption = this.props.newOptionCreator ? this.props.newOptionCreator(originalFilterValue) : {
+ value: originalFilterValue,
+ label: originalFilterValue,
+ create: true
+ };
+ filteredOptions.unshift(newOption);
+ }
}
+ return filteredOptions;
},
renderMenu (options, valueArray, focusedOption) {
@@ -824,6 +844,7 @@ const Select = React.createClass({
onSelect={this.selectValue}
onFocus={this.focusOption}
option={option}
+ addLabelText={this.props.addLabelText}
isSelected={isSelected}
ref={optionRef}
>
diff --git a/test/Select-test.js b/test/Select-test.js
index 6ac47d2900..5d2705344b 100644
--- a/test/Select-test.js
+++ b/test/Select-test.js
@@ -1133,10 +1133,6 @@ describe('Select', () => {
});
describe('with allowCreate=true', () => {
-
- // TODO: allowCreate hasn't been implemented yet in 1.x
- return;
-
beforeEach(() => {
options = [
@@ -1169,17 +1165,18 @@ describe('Select', () => {
it('fires an onChange with the new value when selecting the Add option', () => {
typeSearchText('xyz');
- TestUtils.Simulate.click(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option'));
+ console.log(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option').outerHTML);
+ TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option'));
- expect(onChange, 'was called with', 'xyz');
+ expect(onChange, 'was called with', { value: 'xyz', label: 'xyz', create: true });
});
it('allows updating the options with a new label, following the onChange', () => {
typeSearchText('xyz');
- TestUtils.Simulate.click(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option'));
+ TestUtils.Simulate.mouseDown(ReactDOM.findDOMNode(instance).querySelector('.Select-menu .Select-option'));
- expect(onChange, 'was called with', 'xyz');
+ expect(onChange, 'was called with', { value: 'xyz', label: 'xyz', create: true });
// Now the client adds the option, with a new label
wrapper.setPropsForChild({
@@ -1218,28 +1215,14 @@ describe('Select', () => {
'to have text', 'Add test to values?');
});
- it('does not display the option label when an existing value is entered', () => {
+ it('does not display the add option label when an existing value is entered', () => {
typeSearchText('zzzzz');
expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option'),
'to have length', 1);
expect(ReactDOM.findDOMNode(instance), 'queried for first', '.Select-menu .Select-option',
- 'to have text', 'Add zzzzz to values?');
- });
-
- it('renders the existing option and an add option when an existing display label is entered', () => {
-
- typeSearchText('test value');
-
- // First item should be the add option (as the "value" is not in the collection)
- expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[0],
- 'to have text', 'Add test value to values?');
- // Second item should be the existing option with the matching label
- expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option')[1],
'to have text', 'test value');
- expect(ReactDOM.findDOMNode(instance).querySelectorAll('.Select-menu .Select-option'),
- 'to have length', 2);
});
});
@@ -3226,7 +3209,7 @@ describe('Select', () => {
it('updates the backspace message when the selected values update', () => {
- wrapper.setPropselectorChild({ value: [ 'three', 'two', 'one' ] });
+ wrapper.setPropsForChild({ value: [ 'three', 'two', 'one' ] });
expect(instance,
'to contain',