react-toolbox/components/autocomplete/index.jsx

209 lines
5.6 KiB
React
Raw Normal View History

2015-09-10 21:57:07 +03:00
/* global React */
2015-09-14 10:58:26 +03:00
import { addons } from 'react/addons';
2015-09-10 21:57:07 +03:00
import utils from '../utils';
import Input from '../input';
2015-10-02 20:10:42 +03:00
import style from './style';
2015-09-10 21:57:07 +03:00
2015-10-04 16:12:53 +03:00
export default React.createClass({
2015-09-14 10:58:26 +03:00
mixins: [addons.PureRenderMixin],
2015-09-10 21:57:07 +03:00
displayName: 'Autocomplete',
propTypes: {
className: React.PropTypes.string,
dataSource: React.PropTypes.any,
disabled: React.PropTypes.bool,
error: React.PropTypes.string,
label: React.PropTypes.string,
multiple: React.PropTypes.bool,
onChange: React.PropTypes.func,
required: React.PropTypes.bool,
value: React.PropTypes.any
},
getDefaultProps () {
return {
className: '',
dataSource: {},
2015-10-02 20:10:42 +03:00
multiple: true
2015-09-10 21:57:07 +03:00
};
},
getInitialState () {
return {
2015-09-13 23:05:24 +03:00
dataSource: this._indexDataSource(this.props.dataSource),
2015-09-10 21:57:07 +03:00
focus: false,
2015-09-13 23:05:24 +03:00
query: '',
values: new Map()
2015-09-10 21:57:07 +03:00
};
},
componentDidMount () {
2015-09-13 23:05:24 +03:00
if (this.props.value) this.setValue(this.props.value);
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
componentWillReceiveProps (props) {
if (props.dataSource) {
this.setState({dataSource: this._indexDataSource(props.dataSource)});
2015-09-10 21:57:07 +03:00
}
},
2015-09-13 23:05:24 +03:00
componentWillUpdate (props, state) {
this.refs.input.setValue(state.query);
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
handleQueryChange () {
const query = this.refs.input.getValue();
if (this.state.query !== query) {
this.setState({query: query});
2015-09-10 21:57:07 +03:00
}
},
2015-09-13 23:05:24 +03:00
handleKeyPress (event) {
if (event.which === 13 && this.state.active) {
this._selectOption(this.state.active);
2015-09-10 21:57:07 +03:00
}
2015-09-13 23:05:24 +03:00
if ([40, 38].indexOf(event.which) !== -1) {
const suggestionsKeys = [...this._getSuggestions().keys()];
let index = suggestionsKeys.indexOf(this.state.active) + (event.which === 40 ? +1 : -1);
if (index < 0) index = suggestionsKeys.length - 1;
if (index >= suggestionsKeys.length) index = 0;
this.setState({active: suggestionsKeys[index]});
2015-09-10 21:57:07 +03:00
}
},
2015-09-13 23:05:24 +03:00
handleFocus () {
this.refs.suggestions.getDOMNode().scrollTop = 0;
this.setState({active: '', focus: true});
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
handleBlur () {
2015-09-14 10:58:26 +03:00
if (this.state.focus) this.setState({focus: false});
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
handleHover (event) {
this.setState({active: event.target.getAttribute('id')});
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
handleSelect (event) {
utils.events.pauseEvent(event);
this._selectOption(event.target.getAttribute('id'));
2015-09-10 21:57:07 +03:00
},
2015-09-14 10:58:26 +03:00
handleUnselect (event) {
this._unselectOption(event.target.getAttribute('id'));
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
_indexDataSource (data = {}) {
if (data.length) {
return new Map(data.map((item) => [item, item]));
2015-09-10 21:57:07 +03:00
} else {
2015-09-13 23:05:24 +03:00
return new Map(Object.keys(data).map((key) => [key, data[key]]));
2015-09-10 21:57:07 +03:00
}
},
2015-09-13 23:05:24 +03:00
_getSuggestions () {
let query = this.state.query.toLowerCase().trim() || '';
let suggestions = new Map();
for (let [key, value] of this.state.dataSource) {
if (!this.state.values.has(key) && value.toLowerCase().trim().startsWith(query)) {
suggestions.set(key, value);
2015-09-10 21:57:07 +03:00
}
}
2015-09-13 23:05:24 +03:00
return suggestions;
2015-09-10 21:57:07 +03:00
},
2015-09-13 23:05:24 +03:00
_selectOption (key) {
2015-09-14 10:58:26 +03:00
let { values, dataSource } = this.state;
let query = !this.props.multiple ? dataSource.get(key) : '';
values = new Map(values);
if (!this.props.multiple) values.clear();
values.set(key, dataSource.get(key));
this.setState({focus: false, query: query, values: values}, () => {
this.refs.input.blur();
if (this.props.onChange) this.props.onChange(this);
});
},
_unselectOption (key) {
if (key) {
let values = new Map(this.state.values);
values.delete(key);
this.setState({focus: false, values: values}, () => {
if (this.props.onChange) this.props.onChange(this);
});
2015-09-10 21:57:07 +03:00
}
},
2015-09-13 23:05:24 +03:00
getValue () {
let values = [...this.state.values.keys()];
return this.props.multiple ? values : (values.length > 0 ? values[0] : null);
},
2015-09-10 21:57:07 +03:00
2015-09-13 23:05:24 +03:00
setValue (dataParam = []) {
let values = new Map();
let data = (typeof dataParam === 'string') ? [dataParam] : dataParam;
for (let [key, value] of this.state.dataSource) {
if (data.indexOf(key) !== -1) values.set(key, value);
2015-09-10 21:57:07 +03:00
}
2015-09-13 23:05:24 +03:00
this.setState({values: values, query: this.props.multiple ? '' : values.get(data[0])});
},
2015-09-10 21:57:07 +03:00
2015-09-13 23:05:24 +03:00
setError (data) {
this.input.setError(data);
2015-09-14 10:58:26 +03:00
},
renderSelected () {
if (this.props.multiple) {
return (
2015-10-02 20:10:42 +03:00
<ul data-flex='horizontal wrap' onClick={this.handleUnselect}>
2015-09-14 10:58:26 +03:00
{[...this.state.values].map(([key, value]) => {
2015-10-04 16:12:53 +03:00
return (<li className={style.value} key={key} id={key}>{value}</li>);
2015-09-14 10:58:26 +03:00
})}
</ul>
);
}
},
2015-10-04 16:12:53 +03:00
renderSuggestions () {
return [...this._getSuggestions()].map(([key, value]) => {
let className = style[this.state.active !== key ? 'suggestion' : 'suggestion-active'];
return <li id={key} key={key} className={className}>{value}</li>;
2015-10-02 20:10:42 +03:00
});
2015-10-04 16:12:53 +03:00
},
render () {
let containerClassName = style.container;
let suggestionsClassName = style[this.state.focus ? 'suggestions-active' : 'suggestions'];
if (this.props.className) containerClassName += ` ${this.props.className}`;
2015-09-14 10:58:26 +03:00
return (
2015-10-06 05:42:56 +03:00
<div data-react-toolbox='autocomplete' className={containerClassName}>
2015-10-04 16:12:53 +03:00
{this.props.label ? <label className={style.label}>{this.props.label}</label> : ''}
2015-09-14 10:58:26 +03:00
{this.renderSelected()}
2015-10-02 20:10:42 +03:00
<Input
ref='input'
2015-10-04 16:12:53 +03:00
{...this.props}
2015-10-02 20:10:42 +03:00
label=''
value=''
onBlur={this.handleBlur}
onChange={this.handleQueryChange}
onFocus={this.handleFocus}
onKeyUp={this.handleKeyPress} />
<ul
ref='suggestions'
2015-10-04 16:12:53 +03:00
className={suggestionsClassName}
2015-10-02 20:10:42 +03:00
onMouseDown={this.handleSelect}
onMouseOver={this.handleHover}
>
2015-10-04 16:12:53 +03:00
{this.renderSuggestions()}
2015-09-14 10:58:26 +03:00
</ul>
</div>
);
2015-09-10 21:57:07 +03:00
}
});