diff --git a/README.md b/README.md index e04ce55c..477a7f68 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,13 @@ Default: false If true, options will still be rendered when there is no value. +#### props.selectFirst + +Type: `boolean` +Default: false + +If true, first option will be selected by default. + ### Typeahead ([Exposed Component Functions][reactecf]) #### typeahead.focus diff --git a/dist/react-typeahead.js b/dist/react-typeahead.js index 22ed056b..e2b885f0 100644 --- a/dist/react-typeahead.js +++ b/dist/react-typeahead.js @@ -99,7 +99,7 @@ fuzzy.match = function(pattern, string, opts) { pattern = opts.caseSensitive && pattern || pattern.toLowerCase(); // For each character in the string, either add it to the result - // or wrap in template if its the next string in the pattern + // or wrap in template if it's the next string in the pattern for(var idx = 0; idx < len; idx++) { ch = string[idx]; if(compareString[idx] === pattern[patternIdx]) { @@ -141,8 +141,8 @@ fuzzy.match = function(pattern, string, opts) { // // string to put after matching character // , post: '' // -// // Optional function. Input is an element from the passed in -// // `arr`, output should be the string to test `pattern` against. +// // Optional function. Input is an entry in the given arr`, +// // output should be the string to test `pattern` against. // // In this example, if `arr = [{crying: 'koala'}]` we would return // // 'koala'. // , extract: function(arg) { return arg.crying; } @@ -150,31 +150,31 @@ fuzzy.match = function(pattern, string, opts) { fuzzy.filter = function(pattern, arr, opts) { opts = opts || {}; return arr - .reduce(function(prev, element, idx, arr) { - var str = element; - if(opts.extract) { - str = opts.extract(element); - } - var rendered = fuzzy.match(pattern, str, opts); - if(rendered != null) { - prev[prev.length] = { - string: rendered.rendered - , score: rendered.score - , index: idx - , original: element - }; - } - return prev; - }, []) - - // Sort by score. Browsers are inconsistent wrt stable/unstable - // sorting, so force stable by using the index in the case of tie. - // See http://ofb.net/~sethml/is-sort-stable.html - .sort(function(a,b) { - var compare = b.score - a.score; - if(compare) return compare; - return a.index - b.index; - }); + .reduce(function(prev, element, idx, arr) { + var str = element; + if(opts.extract) { + str = opts.extract(element); + } + var rendered = fuzzy.match(pattern, str, opts); + if(rendered != null) { + prev[prev.length] = { + string: rendered.rendered + , score: rendered.score + , index: idx + , original: element + }; + } + return prev; + }, []) + + // Sort by score. Browsers are inconsistent wrt stable/unstable + // sorting, so force stable by using the index in the case of tie. + // See http://ofb.net/~sethml/is-sort-stable.html + .sort(function(a,b) { + var compare = b.score - a.score; + if(compare) return compare; + return a.index - b.index; + }); }; @@ -554,6 +554,7 @@ var Typeahead = React.createClass({ formInputOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]), defaultClassNames: React.PropTypes.bool, customListComponent: React.PropTypes.oneOfType([React.PropTypes.element, React.PropTypes.func]), + selectFirst: React.PropTypes.bool, showOptionsWhenEmpty: React.PropTypes.bool }, @@ -578,6 +579,7 @@ var Typeahead = React.createClass({ filterOption: null, defaultClassNames: true, customListComponent: TypeaheadSelector, + selectFirst: false, showOptionsWhenEmpty: false }; }, @@ -791,9 +793,14 @@ var Typeahead = React.createClass({ }, componentWillReceiveProps: function (nextProps) { - this.setState({ + var typeaheadOptionsState = { visible: this.getOptionsForValue(this.state.entryValue, nextProps.options) - }); + }; + if (this.props.selectFirst && nextProps.options.length) { + typeaheadOptionsState.selectionIndex = 0; + } + + this.setState(typeaheadOptionsState); }, render: function () { diff --git a/src/typeahead/index.js b/src/typeahead/index.js index a02eaf91..534cee38 100644 --- a/src/typeahead/index.js +++ b/src/typeahead/index.js @@ -48,6 +48,7 @@ var Typeahead = React.createClass({ React.PropTypes.element, React.PropTypes.func ]), + selectFirst: React.PropTypes.bool, showOptionsWhenEmpty: React.PropTypes.bool }, @@ -72,6 +73,7 @@ var Typeahead = React.createClass({ filterOption: null, defaultClassNames: true, customListComponent: TypeaheadSelector, + selectFirst: false, showOptionsWhenEmpty: false }; }, @@ -288,9 +290,14 @@ var Typeahead = React.createClass({ }, componentWillReceiveProps: function(nextProps) { - this.setState({ + var typeaheadOptionsState = { visible: this.getOptionsForValue(this.state.entryValue, nextProps.options) - }); + } + if (this.props.selectFirst && nextProps.options.length) { + typeaheadOptionsState.selectionIndex = 0; + } + + this.setState(typeaheadOptionsState); }, render: function() { diff --git a/test/typeahead-test.js b/test/typeahead-test.js index d62c871c..87a46ae3 100644 --- a/test/typeahead-test.js +++ b/test/typeahead-test.js @@ -580,6 +580,28 @@ describe('Typeahead Component', function() { }); }); + context('selectFirst', function() { + context('options are present', function() { + it('sets the selectionIndex to 0 (first option) by default', function() { + var component = TestUtils.renderIntoDocument(); + component.componentWillReceiveProps({options: BEATLES}) + assert.equal(0, component.state.selectionIndex); + }); + }); + context('options is empty', function() { + it('does not set selectionIndex', function() { + var component = TestUtils.renderIntoDocument(); + component.componentWillReceiveProps({options: []}) + assert.equal(null, component.state.selectionIndex); + }); + }); + + }); + context('showOptionsWhenEmpty', function() { it('do not render options when value is empty by default', function() { var component = TestUtils.renderIntoDocument(