diff --git a/gulpfile.js b/gulpfile.js index 1d6ad18f9f..d8d6de5b7c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,3 +31,33 @@ var taskConfig = { }; initGulpTasks(gulp, taskConfig); + + +/* ========================================================================== + Lib + ========================================================================== */ + +var babel = require('gulp-babel'); +var del = require('del'); + +gulp.task('clean:lib', function(done) { + del('./lib', done); +}); + +gulp.task('build:lib', function() { + return gulp.src([ + './src/**/*.js', + '!**/__tests__/**/*' + ]) + .pipe(babel({ + plugins: [require('babel-plugin-object-assign')] + })) + .pipe(gulp.dest('./lib')); +}); + +gulp.task('watch:lib', ['build:lib'], function() { + return gulp.watch([ + './src/**/*.js', + '!**/__tests__/**/*' + ], ['build:lib']); +}); diff --git a/lib/Select.js b/lib/Select.js index e34513e48a..0be372923f 100644 --- a/lib/Select.js +++ b/lib/Select.js @@ -37,8 +37,9 @@ var Select = React.createClass({ filterOptions: React.PropTypes.func, // method to filter the options array: function([options], filterString, [values]) matchPos: React.PropTypes.string, // (any|start) match the start or entire string when filtering matchProp: React.PropTypes.string, // (any|label|value) which option property to filter on + ignoreCase: React.PropTypes.bool, // whether to perform case-insensitive filtering inputProps: React.PropTypes.object, // custom attributes for the Input (in the Select-control) e.g: {'data-foo': 'bar'} - + allowCreate: React.PropTypes.bool, // wether to allow creation of new entries /* * Allow user to make option label clickable. When this handler is defined we should * wrap label into label tag. @@ -69,7 +70,9 @@ var Select = React.createClass({ className: undefined, matchPos: 'any', matchProp: 'any', + ignoreCase: true, inputProps: {}, + allowCreate: false, onOptionLabelClick: undefined }; @@ -96,11 +99,6 @@ var Select = React.createClass({ componentWillMount: function componentWillMount() { this._optionsCache = {}; this._optionsFilterString = ''; - this.setState(this.getStateFromValue(this.props.value)); - - if (this.props.asyncOptions && this.props.autoload) { - this.autoloadAsyncOptions(); - } var self = this; this._closeMenuIfClickedOutside = function (event) { @@ -122,12 +120,27 @@ var Select = React.createClass({ }; this._bindCloseMenuIfClickedOutside = function () { - document.addEventListener('click', self._closeMenuIfClickedOutside); + if (!document.addEventListener && document.attachEvent) { + document.attachEvent('onclick', this._closeMenuIfClickedOutside); + } else { + document.addEventListener('click', this._closeMenuIfClickedOutside); + } }; this._unbindCloseMenuIfClickedOutside = function () { - document.removeEventListener('click', self._closeMenuIfClickedOutside); + if (!document.removeEventListener && document.detachEvent) { + document.detachEvent('onclick', this._closeMenuIfClickedOutside); + } else { + document.removeEventListener('click', this._closeMenuIfClickedOutside); + } }; + + this.setState(this.getStateFromValue(this.props.value), function () { + //Executes after state change is done. Fixes issue #201 + if (this.props.asyncOptions && this.props.autoload) { + this.autoloadAsyncOptions(); + } + }); }, componentWillUnmount: function componentWillUnmount() { @@ -211,6 +224,7 @@ var Select = React.createClass({ inputValue: '', filteredOptions: filteredOptions, placeholder: !this.props.multi && values.length ? values[0].label : this.props.placeholder, + placeholderIcon: !this.props.multi && values.length ? values[0].iconClass : null, focusedOption: !this.props.multi && values.length ? values[0] : filteredOptions[0] }; }, @@ -281,7 +295,7 @@ var Select = React.createClass({ }, resetValue: function resetValue() { - this.setValue(this.state.value); + this.setValue(this.state.value === '' ? null : this.state.value); }, getInputNode: function getInputNode() { @@ -371,6 +385,8 @@ var Select = React.createClass({ case 13: // enter + if (!this.state.isOpen) return; + this.selectFocusedOption(); break; @@ -393,6 +409,15 @@ var Select = React.createClass({ this.focusNextOption(); break; + case 188: + // , + if (this.props.allowCreate) { + event.preventDefault(); + event.stopPropagation(); + this.selectFocusedOption(); + } + break; + default: return; } @@ -464,7 +489,7 @@ var Select = React.createClass({ } } this.setState(newState); - if (callback) callback({}); + if (callback) callback.call(this, {}); return; } } @@ -493,7 +518,7 @@ var Select = React.createClass({ } self.setState(newState); - if (callback) callback({}); + if (callback) callback.call(self, {}); }); }, @@ -514,13 +539,21 @@ var Select = React.createClass({ if (this.props.filterOption) return this.props.filterOption.call(this, op, filterValue); var valueTest = String(op.value), labelTest = String(op.label); - return !filterValue || this.props.matchPos === 'start' ? this.props.matchProp !== 'label' && valueTest.toLowerCase().substr(0, filterValue.length) === filterValue || this.props.matchProp !== 'value' && labelTest.toLowerCase().substr(0, filterValue.length) === filterValue : this.props.matchProp !== 'label' && valueTest.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0 || this.props.matchProp !== 'value' && labelTest.toLowerCase().indexOf(filterValue.toLowerCase()) >= 0; + if (this.props.ignoreCase) { + valueTest = valueTest.toLowerCase(); + labelTest = labelTest.toLowerCase(); + filterValue = filterValue.toLowerCase(); + } + return !filterValue || this.props.matchPos === 'start' ? this.props.matchProp !== 'label' && valueTest.substr(0, filterValue.length) === filterValue || this.props.matchProp !== 'value' && labelTest.substr(0, filterValue.length) === filterValue : this.props.matchProp !== 'label' && valueTest.indexOf(filterValue) >= 0 || this.props.matchProp !== 'value' && labelTest.indexOf(filterValue) >= 0; }; return (options || []).filter(filterOption, this); } }, selectFocusedOption: function selectFocusedOption() { + if (this.props.allowCreate && !this.state.focusedOption) { + return this.selectValue(this.state.inputValue); + } return this.selectValue(this.state.focusedOption); }, @@ -596,6 +629,15 @@ var Select = React.createClass({ if (this.state.filteredOptions.length > 0) { focusedValue = focusedValue == null ? this.state.filteredOptions[0] : focusedValue; } + // Add the current value to the filtered options in last resort + if (this.props.allowCreate && this.state.inputValue.trim()) { + var inputValue = this.state.inputValue; + this.state.filteredOptions.unshift({ + value: inputValue, + label: inputValue, + create: true + }); + } var ops = Object.keys(this.state.filteredOptions).map(function (key) { var op = this.state.filteredOptions[key]; @@ -617,13 +659,22 @@ var Select = React.createClass({ return React.createElement( 'div', { ref: ref, key: 'option-' + op.value, className: optionClass }, + React.createElement('i', { className: op.iconClass }), + ' ', op.label ); } else { return React.createElement( 'div', - { ref: ref, key: 'option-' + op.value, className: optionClass, onMouseEnter: mouseEnter, onMouseLeave: mouseLeave, onMouseDown: mouseDown, onClick: mouseDown }, - op.label + { ref: ref, key: 'option-' + op.value, + className: optionClass, + onMouseEnter: mouseEnter, + onMouseLeave: mouseLeave, + onMouseDown: mouseDown, + onClick: mouseDown }, + React.createElement('i', { className: op.iconClass }), + ' ', + op.create ? 'Add ' + op.label + ' ?' : op.label ); } }, this); @@ -677,6 +728,7 @@ var Select = React.createClass({ value.push(React.createElement( 'div', { className: 'Select-placeholder', key: 'placeholder' }, + React.createElement('i', { className: this.state.placeholderIcon }), this.state.placeholder )); } diff --git a/package.json b/package.json index 6a3320a107..97eff42e74 100644 --- a/package.json +++ b/package.json @@ -14,24 +14,22 @@ "react-input-autosize": "^0.4.3" }, "devDependencies": { + "babel-core": "^5.4.7", "babel-eslint": "^3.1.1", "babel-jest": "^5.2.0", + "babel-plugin-object-assign": "^1.1.0", + "del": "^1.2.0", "eslint": "^0.22.1", "eslint-plugin-react": "^2.2.0", "gulp": "^3.8.11", + "gulp-babel": "^5.1.0", "jest-cli": "^0.4.2", - "lessify": "^1.0.1", "react": ">=0.12.0", "react-component-gulp-tasks": "^0.7.0" }, "peerDependencies": { "react": ">=0.12.0" }, - "browserify": { - "transform": [ - "lessify" - ] - }, "browserify-shim": { "classnames": "global:classNames", "react": "global:React", diff --git a/src/Select.js b/src/Select.js index ca53283af0..33fb6c1642 100644 --- a/src/Select.js +++ b/src/Select.js @@ -220,6 +220,7 @@ var Select = React.createClass({ inputValue: '', filteredOptions: filteredOptions, placeholder: !this.props.multi && values.length ? values[0].label : this.props.placeholder, + placeholderIcon: !this.props.multi && values.length ? values[0].iconClass : null, focusedOption: !this.props.multi && values.length ? values[0] : filteredOptions[0] }; }, @@ -651,9 +652,22 @@ var Select = React.createClass({ var mouseDown = this.selectValue.bind(this, op); if (op.disabled) { - return