focus-components-v3
Version:
Focus web components to build applications (based on Material Design)
196 lines (176 loc) • 6.76 kB
JavaScript
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import i18next from 'i18next';
import MDBehaviour from '../behaviours/material';
import {InputBehaviour} from '../behaviours/input-component';
import debounce from 'lodash/debounce';
class AutocompleteTextEdit extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: this.props.rawInputValue,
suggestions: [],
hasSuggestions: false,
error: this.props.error,
hasFocus: false,
isLoading: false
};
}
componentWillReceiveProps({error}) {
if(error) {
this.setState({error: error});
}
}
componentDidMount() {
const {inputTimeout} = this.props;
this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
}
// Returns the state's inputValue
getValue = () => {
const {inputValue} = this.state;
if(inputValue !== undefined) {
return inputValue;
}
else {
return null;
}
};
// Gets the defined props' querySearch and returns the object given by the promise
// Sets the hasSuggestions' state if the given object has a none empty array
_querySearcher = value => {
const {querySearcher} = this.props;
const {hasSuggestions} = this.state;
querySearcher(value).then(({data, totalCount}) => {
if(totalCount > 0) {
this.setState({hasSuggestions: true, suggestions: data, error: ''});
}
this.refs.loader.classList.remove('mdl-progress__indeterminate');
this.setState({isLoading: false});
}).catch(err => {
this.refs.loader.classList.remove('mdl-progress__indeterminate');
this.setState({error: JSON.stringify(err), isLoading: false});
this.refs.materialInput.classList.add('is-invalid');
});
};
// Sets the state's inputValue when the user is typing
_handleonChange = ({target: {value}}) => {
const {onChange} = this.props;
this.setState({inputValue: value});
if(value.trim() == '') {
this.setState({hasSuggestions: false});
}
else {
this.refs.loader.classList.add('mdl-progress__indeterminate');
this.setState({isLoading: true});
this._debouncedQuerySearcher(value);
if (onChange) onChange(value);
}
};
// Sets the value input with the selected suggestion and hides the dropdown
onResultClick(value) {
const {onChange} = this.props;
this.refs.inputText.value = value;
this.setState({inputValue: value, hasSuggestions: false, suggestions: []});
if (onChange) onChange(value);
return value;
};
// Returns an html list whith the Suggestions
renderSuggestions = () => {
const {suggestions} = this.state;
const allSuggestions = suggestions.map(({key, label}) => <li key={key} onMouseDown={(e) => {this.onResultClick(label); e.preventDefault();}} data-focus='option' >{label}</li>);
return(
<ul ref='suggestions' data-focus='options'>
{allSuggestions}
</ul>
);
};
// Behaviour when onFocus and onBlur are triggered
_handleonFocus = e => {
const {hasSuggestions, hasFocus} = this.state;
const {showAtFocus, emptyShowAll} = this.props;
this.setState({hasFocus: !this.state.hasFocus});
if(hasSuggestions && !showAtFocus && hasFocus === false) {
this.setState({hasSuggestions: false});
}
if(!hasSuggestions && e.target.value.trim() === '' && emptyShowAll && hasFocus === false) {
// Doing a global search here
this._querySearcher('');
}
return true;
};
// Maybe give the option for the floating label
render() {
const {inputValue, hasSuggestions, hasFocus, isLoading} = this.state;
const validInputProps = this._checkProps(this.props);
const {inputTimeout, error, placeholder} = this.props;
validInputProps.value = inputValue === undefined || inputValue === null ? '' : inputValue;
validInputProps.onBlur = this._handleonFocus;
const inputProps = {...validInputProps};
return(
<div data-focus='autocompleteText'>
<div className={`mdl-textfield mdl-js-textfield${error ? ' is-invalid' : ''}`} ref='materialInput'>
<div data-focus='loading' data-loading={isLoading} className='mdl-progress mdl-js-progress' ref='loader'/>
<input className='mdl-textfield__input' type='text' ref='inputText' {...inputProps}/>
<label className="mdl-textfield__label">{i18next.t(placeholder)}</label>
<span className="mdl-textfield__error" ref='errorMessage'>{i18next.t(error)}</span>
</div>
{hasSuggestions && hasFocus &&
this.renderSuggestions()
}
</div>
);
}
}
AutocompleteTextEdit.displayName = 'AutocompleteTextEdit';
AutocompleteTextEdit.defaultProps = {
emptyShowAll: false,
inputTimeout: 200,
placeholder: 'Search here...',
showAtFocus: false
};
AutocompleteTextEdit.propTypes = {
emptyShowAll: PropTypes.bool, //Defines if it shows suggestions on focus when the input is empty.
error: PropTypes.string, //Error showed message.
inputTimeout : PropTypes.number.isRequired, //Timeout to execute the debounce function.
onChange: PropTypes.func, //Launches the querySearcher.
placeholder: PropTypes.string, //Placeholder field.
querySearcher: PropTypes.func.isRequired, //Returns a promise which is connected to the web service.
rawInputValue: PropTypes.string, //Field value.
showAtFocus: PropTypes.bool //Defines it shows suggestions on focus.
};
export default AutocompleteTextEdit;
/*
EXAMPLE
const _querySearcher = query => {
let data = [
{
key: 'JL',
label: 'Joh Lickeur'
},
{
key: 'GK',
label: 'Guénolé Kikabou'
},
{
key: 'YL',
label: 'Yannick Lounivis'
}
];
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data,
totalCount: data.length
});
}, 500);
});
};
<AutocompleteText
isEdit={isEdit}
querySearcher={_querySearcher}
placeholder={'Your search...'}
error{Something wrong happend. Retry please...}
/>
*/