react-toolbox/components/autocomplete/index.jsx

209 lines
5.6 KiB
JavaScript

/* global React */
import { addons } from 'react/addons';
import utils from '../utils';
import Input from '../input';
import style from './style';
export default React.createClass({
mixins: [addons.PureRenderMixin],
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: {},
multiple: true
};
},
getInitialState () {
return {
dataSource: this._indexDataSource(this.props.dataSource),
focus: false,
query: '',
values: new Map()
};
},
componentDidMount () {
if (this.props.value) this.setValue(this.props.value);
},
componentWillReceiveProps (props) {
if (props.dataSource) {
this.setState({dataSource: this._indexDataSource(props.dataSource)});
}
},
componentWillUpdate (props, state) {
this.refs.input.setValue(state.query);
},
handleQueryChange () {
const query = this.refs.input.getValue();
if (this.state.query !== query) {
this.setState({query: query});
}
},
handleKeyPress (event) {
if (event.which === 13 && this.state.active) {
this._selectOption(this.state.active);
}
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]});
}
},
handleFocus () {
this.refs.suggestions.getDOMNode().scrollTop = 0;
this.setState({active: '', focus: true});
},
handleBlur () {
if (this.state.focus) this.setState({focus: false});
},
handleHover (event) {
this.setState({active: event.target.getAttribute('id')});
},
handleSelect (event) {
utils.events.pauseEvent(event);
this._selectOption(event.target.getAttribute('id'));
},
handleUnselect (event) {
this._unselectOption(event.target.getAttribute('id'));
},
_indexDataSource (data = {}) {
if (data.length) {
return new Map(data.map((item) => [item, item]));
} else {
return new Map(Object.keys(data).map((key) => [key, data[key]]));
}
},
_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);
}
}
return suggestions;
},
_selectOption (key) {
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);
});
}
},
getValue () {
let values = [...this.state.values.keys()];
return this.props.multiple ? values : (values.length > 0 ? values[0] : null);
},
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);
}
this.setState({values: values, query: this.props.multiple ? '' : values.get(data[0])});
},
setError (data) {
this.input.setError(data);
},
renderSelected () {
if (this.props.multiple) {
return (
<ul data-flex='horizontal wrap' onClick={this.handleUnselect}>
{[...this.state.values].map(([key, value]) => {
return (<li className={style.value} key={key} id={key}>{value}</li>);
})}
</ul>
);
}
},
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>;
});
},
render () {
let containerClassName = style.container;
let suggestionsClassName = style[this.state.focus ? 'suggestions-active' : 'suggestions'];
if (this.props.className) containerClassName += ` ${this.props.className}`;
return (
<div data-react-toolbox='autocomplete' className={containerClassName}>
{this.props.label ? <label className={style.label}>{this.props.label}</label> : ''}
{this.renderSelected()}
<Input
ref='input'
{...this.props}
label=''
value=''
onBlur={this.handleBlur}
onChange={this.handleQueryChange}
onFocus={this.handleFocus}
onKeyUp={this.handleKeyPress} />
<ul
ref='suggestions'
className={suggestionsClassName}
onMouseDown={this.handleSelect}
onMouseOver={this.handleHover}
>
{this.renderSuggestions()}
</ul>
</div>
);
}
});