feat(combobox): Improved change event implementation
Component's state is now updated before triggering the change event. The event handler is executed only when actual change occurred Autocomplete and DropDownList are strongly affected by this commit, due to changes in the List (visible property that should be removed after implementing the Popup), ListItem and SearchBar
This commit is contained in:
Родитель
c880a88142
Коммит
4c9595c518
|
@ -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" },
|
||||
|
|
|
@ -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 (
|
||||
<span {...comboBoxProps}>
|
||||
|
|
34
src/List.jsx
34
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 (
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,8 +39,9 @@ describe('ListItem', () => {
|
|||
|
||||
it('should pass dataItem to the click handler', () => {
|
||||
const spy = jasmine.createSpy('spy');
|
||||
result = shallow(<ListItem dataItem={dataItem} onClick={spy} />);
|
||||
const index = 1;
|
||||
result = shallow(<ListItem dataItem={dataItem} onClick={spy} index={index}/>);
|
||||
result.simulate('click');
|
||||
expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 });
|
||||
expect(spy).toHaveBeenCalledWith({ text: 'foo', value: 1 }, index);
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче