diff --git a/examples/combobox.jsx b/examples/combobox.jsx index ba037f5..5008969 100644 --- a/examples/combobox.jsx +++ b/examples/combobox.jsx @@ -35,7 +35,6 @@ const data = [ { text: "Malta", value: "Mal" }, { text: "Moldova", value: "Mol" }, { text: "Monaco", value: "Mon" }, - { text: "Montenegro", value: "Mon" }, { text: "Netherlands", value: "Net" }, { text: "Norway", value: "Nor" }, { text: "Poland", value: "Pol" }, diff --git a/src/ComboBox.jsx b/src/ComboBox.jsx index 0105ab5..0ff81b5 100644 --- a/src/ComboBox.jsx +++ b/src/ComboBox.jsx @@ -46,9 +46,12 @@ const defaultProps = { class ComboBox extends React.Component { constructor(props) { super(props); + this._oldText = ""; + this._oldValue = ""; this.state = { dataItem: null, - value: this.props.value || null, + expanded: false, + value: this.props.value || "", focused: null }; } @@ -58,17 +61,30 @@ class ComboBox extends React.Component { if (suggest && data.length) { this.setState({ + expanded: data.length > 0, word: data[0][textField], highlight: true, - focused: itemIndex(this.text, data, textField) //get focused item on filtered data + focused: itemIndex(this.text, data, textField) //filtered data focused item }); } } - handleChange = () => { + handleChange = (text) => { + if (this._oldText === text || this._oldValue === this.state.value) { + return; + } + + this._oldText = this.state.text; + this._oldValue = this.state.value; this.props.onChange(this.state.value); } + handleBlur = () => { + if (!this.state.dateItem) { + this.selectFocused(); + } + }; + handleFilter = (word) => { const minLength = this.props.minLength; if (word.length >= minLength) { @@ -77,9 +93,9 @@ class ComboBox extends React.Component { }; toggle = () => { - //this.setState({ - // expanded: !this.state.expanded - // }); + this.setState({ + expanded: !this.state.expanded //TODO: check data.length + }); }; navigate = (keyCode) => { @@ -103,37 +119,45 @@ class ComboBox extends React.Component { }; textUpdate = (text) => { - this.text = text; + const index = itemIndex(text, this.props.data, this.props.textField); //unfiltered data focused item + const dataItem = this.props.data[index]; + this.text = text; this.setState({ + expanded: index >= 0, text: text, - word: null, - highlight: false, - focused: itemIndex(text, this.props.data, this.props.textField) //get focused item on unfiltered data + word: dataItem && this.props.suggest ? this.props.data[index][this.props.textField] : null, + highlight: true, + focused: index }); }; - select = (dataItem) => { + select = (dataItem, index) => { const value = dataItem ? dataItem[this.props.valueField] : this.refs.searchBar._input.value; const text = dataItem ? dataItem[this.props.textField] : this.refs.searchBar._input.value; this.setState({ + dataItem: dataItem ? dataItem : null, text: text, value: value, - word: text, - highlight: false - }); + highlight: false, + focused: index, + expanded: false + }, function() { + this.handleChange(value); + }.bind(this)); }; selectFocused = () => { const focused = this.state.focused; if (focused !== null) { - this.select(this.props.data[focused]); + this.select(this.props.data[focused], focused); } }; render() { const searchBarProps = { + blur: this.handleBlur, filter: this.handleFilter, change: this.textUpdate, handleChange: this.handleChange, @@ -154,6 +178,9 @@ class ComboBox extends React.Component { const buttonProps = { onClick: this.toggle, + onMouseDown: function(event) { + event.preventDefault(); + }, className: buttonClasses, icon: "arrow-s" }; @@ -177,7 +204,8 @@ class ComboBox extends React.Component { renderer: this.props.itemRenderer, onClick: this.select, textField: this.props.textField, - valueField: this.props.valueField + valueField: this.props.valueField, + visible: this.state.expanded }; return ( diff --git a/src/List.jsx b/src/List.jsx index de6ff0b..ddfe35c 100644 --- a/src/List.jsx +++ b/src/List.jsx @@ -17,7 +17,12 @@ export default class List extends React.Component { onClick: PropTypes.func, selected: PropTypes.number, textField: PropTypes.string, - valueField: PropTypes.string + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string + ]), + valueField: PropTypes.string, + visible: PropTypes.bool }; constructor(props) { @@ -31,8 +36,8 @@ export default class List extends React.Component { } } - clickHandler = (dataItem) => { - this.props.onClick(dataItem); + clickHandler = (dataItem, index) => { + this.props.onClick(dataItem, index); }; renderItems() { @@ -44,6 +49,7 @@ export default class List extends React.Component { dataItem={item} focused={index === focused} key={util.getter(item, valueField)} + index={index} onClick={this.clickHandler} renderer={itemRenderer} selected={index === selected} @@ -55,7 +61,27 @@ export default class List extends React.Component { render() { const style = { - height: 140 + height: 140, + display: this.props.visible ? "block" : "none", + height: this.props.height || 200, + overflowY: "scroll" //TODO: remove after popup is added + }; + + const { + focused, + value, + defaultItem, + textField, + itemRenderer + } = this.props; + + const defaultItemProps = { + focused: focused === -1, + selected: value === undefined, + textField: textField, + dataItem: defaultItem, + onClick: this.clickHandler, + renderer: itemRenderer }; return ( diff --git a/src/ListItem.jsx b/src/ListItem.jsx index 6e4539e..ce70f4e 100644 --- a/src/ListItem.jsx +++ b/src/ListItem.jsx @@ -12,6 +12,7 @@ export default class ListItem extends React.Component { PropTypes.string ]), focused: PropTypes.bool, + index: PropTypes.number, onClick: PropTypes.func, renderer: PropTypes.func, selected: PropTypes.bool, @@ -37,7 +38,7 @@ export default class ListItem extends React.Component { }; onClick = () => { - this.props.onClick(this.props.dataItem); + this.props.onClick(this.props.dataItem, this.props.index); }; render() { diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 7cb142d..df17fd2 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -5,6 +5,7 @@ import { caretIndex, indexOfWordAtCaret, caretSelection, textReduced, replaceWor export default class SearchBar extends React.Component { static propTypes = { + blur: PropTypes.func, change: PropTypes.func, disabled: PropTypes.bool, filter: PropTypes.func, @@ -18,7 +19,11 @@ export default class SearchBar extends React.Component { word: PropTypes.string }; - //TODO add defaultProps + static defaultProps = { + blur() {}, + handleChange() {} + }; + constructor(props) { super(props); @@ -47,7 +52,8 @@ export default class SearchBar extends React.Component { } else { //only when something is chosen from the list caretSelection(this._input, this._input.value.length); - this.props.handleChange(this._input.value); + // console.log("call handleChange from SearchBar.componentDidUpdate") + // this.props.handleChange(this._input.value); } } else { //in every other case @@ -64,11 +70,8 @@ export default class SearchBar extends React.Component { }; onBlur = () => { + this.props.blur(); window.document.removeEventListener("selectionchange", this.onSelectionChange); - //only when NOT chosen from list - if (!this.props.word || (this.props.word && this.props.highlight)) { - this.props.handleChange(this._input.value); - } }; onKeyDown = (event) => { diff --git a/test/List.jsx b/test/List.jsx index 58f54cc..e2e12d6 100644 --- a/test/List.jsx +++ b/test/List.jsx @@ -50,10 +50,10 @@ describe('List', () => { const items = result.find(ListItem); items.at(0).shallow().simulate('click'); - expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 }); + expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 }, 0); items.at(1).shallow().simulate('click'); - expect(spy).toHaveBeenCalledWith({ text: 'bar', value: 2 }); + expect(spy).toHaveBeenCalledWith({ text: 'bar', value: 2 }, 1); }); it('should fire onClick (array of strings)', () => { @@ -62,9 +62,9 @@ describe('List', () => { const items = result.find(ListItem); items.at(0).shallow().simulate('click'); - expect(spy).toHaveBeenCalledWith("foo"); + expect(spy).toHaveBeenCalledWith("foo", 0); items.at(1).shallow().simulate('click'); - expect(spy).toHaveBeenCalledWith("bar"); + expect(spy).toHaveBeenCalledWith("bar", 1); }); }); diff --git a/test/ListItem.jsx b/test/ListItem.jsx index 0a8c33d..728b7ae 100644 --- a/test/ListItem.jsx +++ b/test/ListItem.jsx @@ -39,8 +39,9 @@ describe('ListItem', () => { it('should pass dataItem to the click handler', () => { const spy = jasmine.createSpy('spy'); - result = shallow(); + const index = 1; + result = shallow(); result.simulate('click'); - expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 }); + expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 }, index); }); });