@bigfishtv/cockpit
Version:
204 lines (187 loc) • 5.56 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import deepEqual from 'deep-equal'
import keycode from 'keycode'
import _debounce from 'lodash/debounce'
import Autosuggest from 'react-autosuggest'
import { get } from '../../api/xhrUtils'
import Button from '../button/Button'
import autosuggestTheme from '../../config/autosuggestTheme'
import Icon from '../Icon'
import Spinner from '../Spinner'
// we define this because react-docgen fails when defaultProp directly references an imported component
const autosuggestThemeDefault = { ...autosuggestTheme }
export default class AutosuggestInput extends Component {
static propTypes = {
placeholder: PropTypes.string,
renderSuggestion: PropTypes.func,
queryUrl: PropTypes.string,
onChange: PropTypes.func,
getSuggestions: PropTypes.func,
readOnly: PropTypes.bool,
queryParam: PropTypes.string,
}
static defaultProps = {
placeholder: 'Search...',
theme: autosuggestThemeDefault,
renderSuggestion: (suggestion, { value, valueBeforeUpDown }) => (
<div>{suggestion.name || suggestion.title || suggestion.toString()}</div>
),
queryParam: 'q',
params: {},
autoCache: true,
autoFocus: false,
multiSection: false,
debounceTime: 100,
suggestions: [],
onInputFocus: () => {},
onInputBlur: () => {},
onInputChange: () => {},
onSuggestionsChange: () => {},
filterSuggestions: suggestions => suggestions,
onChange: () => {},
onCreate: () => {},
beforeSelection: () => true,
getSectionSuggestions: section => section.suggestions,
renderSectionTitle: section => section.sectionName,
Cell: null,
}
constructor(props) {
super(props)
this.cache = {}
this.focused = props.autoFocus || false
this.handleKeyDown = this.handleKeyDown.bind(this)
this.getSuggestions = _debounce(this.getSuggestions, props.debounceTime)
this.state = {
loading: false,
inputValue: '',
suggestions: props.suggestions || [],
}
}
componentDidMount() {
document.addEventListener('keydown', this.handleKeyDown)
}
componentWillUnmount() {
document.removeEventListener('keydown', this.handleKeyDown)
}
componentWillReceiveProps(nextProps) {
if (!deepEqual(this.props.suggestions, nextProps.suggestions)) {
this.setState({ suggestions: nextProps.suggestions })
}
}
handleKeyDown(event) {
if (keycode(event) == 'enter' && this.focused && !this.state.suggestions.length) {
event.preventDefault()
this.props.onCreate(this.state.inputValue)
this.setState({ inputValue: '' })
}
}
// When suggestion is selected either via click/tap or enter key
handleSuggestion = (event, { suggestion, suggestionValue, method }) => {
event.preventDefault()
if (this.props.beforeSelection(suggestion, suggestionValue, method)) {
this.props.onChange(suggestion)
this.setState({ inputValue: '' })
}
}
// Called upon 'onSuggestionsUpdateRequested'
getSuggestions = ({ value, reason }) => {
if (reason == 'type') {
if (this.props.autoCache && value in this.cache) {
this.applySuggestions(this.cache[value])
} else {
this.setState({ loading: true })
get({
url: this.props.queryUrl,
params: { ...this.props.params, [this.props.queryParam]: value },
callback: data => {
this.setState({ loading: false })
setTimeout(() => this.applySuggestions(data), 10)
},
})
}
}
}
// Called once suggestions are fetched, either from cache or via xhr
applySuggestions(preFilteredSuggestions) {
const suggestions = this.props.filterSuggestions(preFilteredSuggestions)
this.setState({ suggestions })
this.props.onSuggestionsChange(suggestions)
}
render() {
const {
autoFocus,
autoCache,
multiSection,
getSuggestions,
getSectionSuggestions,
renderSuggestion,
renderSectionTitle,
readOnly,
placeholder,
theme,
onInputChange,
onInputFocus,
onInputBlur,
Cell,
...rest
} = this.props
const { suggestions, inputValue, loading } = this.state
const inputProps = {
value: inputValue,
placeholder,
readOnly,
autoFocus,
onChange: (event, { newValue, method }) => {
if (method == 'type') {
this.setState({ inputValue: newValue })
onInputChange(newValue)
}
},
onBlur: event => {
this.focused = false
onInputBlur(event)
},
onFocus: event => {
this.focused = true
onInputFocus(event)
},
}
if (!this.props.value) {
return (
<div className="autosuggest-container">
<Autosuggest
ref="autosuggest"
theme={theme}
multiSection={multiSection}
suggestions={suggestions}
onSuggestionsUpdateRequested={getSuggestions || this.getSuggestions}
getSuggestionValue={() => ''}
getSectionSuggestions={getSectionSuggestions}
shouldRenderSuggestions={value => typeof value == 'undefined' || !!value}
renderSuggestion={renderSuggestion}
renderSectionTitle={renderSectionTitle}
onSuggestionSelected={this.handleSuggestion}
inputProps={inputProps}
/>
{loading && <Spinner size={16} />}
</div>
)
} else {
if (Cell) {
return <Cell {...rest} value={this.props.value} onRemove={() => this.props.onChange(null)} />
} else {
return (
<div className="cell">
<div className="cell-content">{this.props.renderSuggestion(this.props.value)}</div>
<div className="cell-control">
<Button size="icon" onClick={() => this.props.onChange(null)}>
<Icon name="close" size="18" />
</Button>
</div>
</div>
)
}
}
}
}