diff --git a/src/components/input/autocomplete-select/consult.js b/src/components/input/autocomplete-select/consult.js
index a9e3389bd..805985020 100644
--- a/src/components/input/autocomplete-select/consult.js
+++ b/src/components/input/autocomplete-select/consult.js
@@ -1,25 +1,58 @@
-import React, { Component } from 'react';
+import React, { Component, PropTypes } from 'react';
import ComponentBaseBehaviour from '../../../behaviours/component-base';
@ComponentBaseBehaviour
+/**
+ * Autcomplete select component consultation view.
+ */
class AutocompleteSelectConsult extends Component {
+ /** DisplayName. */
+ static displayName = 'AutocompleteSelectConsult';
+
+ /** PropTypes. */
+ static propTypes = {
+ keyName: PropTypes.string,
+ keyResolver: PropTypes.func.isRequired,
+ label: PropTypes.string.isRequired,
+ labelName: PropTypes.string,
+ name: PropTypes.string.isRequired,
+ type: PropTypes.string.isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
+ wholeItem: PropTypes.bool
+ };
+
+ /** DefaultProps. */
+ static defaultProps = {
+ keyName: 'key',
+ labelName: 'label',
+ wholeItem: false
+ };
+
+ /** Initial state. */
state = {};
+ /** @inheritdoc */
componentDidMount() {
this._callKeyResolver(this.props.value);
}
+ /** @inheritdoc */
componentWillReceiveProps({ value }) {
if (value !== this.props.value) {
this._callKeyResolver(value);
}
}
+ /**
+ * Callback to resolve value into label.
+ * @param {string} value value.
+ */
_callKeyResolver(value) {
- const { keyResolver } = this.props;
+ const { keyResolver, keyName, wholeItem } = this.props;
if (keyResolver && value !== undefined && value !== null) {
- keyResolver(value).then(label => {
+ const key = wholeItem ? value[keyName] : value;
+ keyResolver(key).then(label => {
this.setState({ resolvedLabel: label });
}).catch(err => { console.error(err.message); });
} else {
@@ -27,9 +60,10 @@ class AutocompleteSelectConsult extends Component {
}
}
+ /** @inheritdoc */
render() {
- const { label, name, type, value } = this.props;
- const { resolvedLabel = value } = this.state;
+ const { label, name, type, value, wholeItem, labelName } = this.props;
+ const { resolvedLabel = wholeItem ? value[labelName] : value } = this.state;
return (
{
- this.setState({ inputValue, fromKeyResolver: true });
- }).catch(error => this.setState({ customError: error.message }));
+ if (value !== undefined && value !== null) {
+ this._setValue(this.props);
}
document.addEventListener('click', this._handleDocumentClick);
this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout);
}
- componentWillReceiveProps({ value, customError, error, keyResolver }) {
+ /** @inheritdoc */
+ componentWillReceiveProps(nextProps) {
+ const { value, customError, error, labelName, wholeItem } = nextProps;
if (value !== this.props.value && value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
- this.setState({ inputValue: value, customError }, () => keyResolver(value).then(inputValue => {
- this.setState({ inputValue, fromKeyResolver: true });
- }).catch(error => this.setState({ customError: error.message })));
+ const inputValue = wholeItem ? value[labelName] : value;
+ this.setState({ inputValue, customError }, () => this._setValue(nextProps));
} else if (customError !== this.props.customError) {
this.setState({ customError });
}
@@ -90,6 +116,7 @@ class Autocomplete extends Component {
}
}
+ /** @inheritdoc */
componentDidUpdate() {
if (this.props.customError) {
this.refs.inputText.classList.add('is-invalid');
@@ -98,26 +125,54 @@ class Autocomplete extends Component {
}
}
+ /** @inheritdoc */
componentWillUnmount() {
document.removeEventListener('click', this._handleDocumentClick);
}
+
+ /**
+ * Set value.
+ *
+ * @param {object} object containing props
+ * @memberof AutocompleteSelectEdit
+ */
+ _setValue({ value, keyResolver, labelName, wholeItem }) {
+
+ if (wholeItem) {
+ this.setState({ inputValue: value[labelName], fromKeyResolver: true });
+ } else {
+ keyResolver(value).then(inputValue => {
+ this.setState({ inputValue, fromKeyResolver: true });
+ }).catch(error => this.setState({ customError: error.message }));
+ }
+ }
+
+ /**
+ * Get value.
+ * @returns {string} value.
+ */
getValue() {
- const { labelName, keyName, value } = this.props;
+ const { labelName, value, wholeItem } = this.props;
const { inputValue, selected, options, fromKeyResolver } = this.state;
- const resolvedLabel = options.get(selected);
+ const item = options.get(selected) || {};
+ const resolvedLabel = item[labelName];
if (inputValue === '') { // The user cleared the field, return a null
return null;
- } else if (fromKeyResolver) { // Value was received from the keyResolver, give it firectly
+ } else if (fromKeyResolver) { // Value was received from the keyResolver, give it directly
return value;
} else if (resolvedLabel !== inputValue && selected !== inputValue) { // The user typed something without selecting any option, return a null
return null;
} else { // The user selected an option (or no value was provided), return it
- return selected || null;
+ return wholeItem ? item : selected || null;
}
}
- _handleDocumentClick = ({ target }) => {
+ /**
+ * Handle document click.
+ * @param {object} event event.
+ */
+ _handleDocumentClick({ target }) {
const { focus, inputValue } = this.state;
const { onBadInput } = this.props;
if (focus) {
@@ -130,40 +185,56 @@ class Autocomplete extends Component {
});
}
}
- };
+ }
- _handleQueryChange = ({ target: { value } }) => {
+ /**
+ * Handle query change.
+ * @param {object} event event.
+ */
+ _handleQueryChange({ target: { value } }) {
if (value === '') { // the user cleared the input, don't call the querySearcher
const { onChange } = this.props;
this.setState({ inputValue: value, fromKeyResolver: false });
- if (onChange) onChange(null);
+ if (onChange) {
+ onChange(null);
+ }
} else {
this.setState({ inputValue: value, fromKeyResolver: false, isLoading: true });
this._debouncedQuerySearcher(value);
}
- };
+ }
- _querySearcher = value => {
- const { querySearcher, keyName, labelName } = this.props;
+ /**
+ * Query searcher.
+ * @param {string} value value.
+ */
+ _querySearcher(value) {
+ const { querySearcher, keyName } = this.props;
querySearcher(value).then(({ data, totalCount }) => {
- // TODO handle the incomplete option list case
const options = new Map();
data.forEach(item => {
- options.set(item[keyName], item[labelName]);
+ options.set(item[keyName], item);
});
this.setState({ options, isLoading: false, totalCount });
}).catch(error => this.setState({ customError: error.message }));
- };
+ }
- _handleQueryFocus = () => {
+ /**
+ * Handle query field focus.
+ */
+ _handleQueryFocus() {
this.refs.options.scrollTop = 0;
if (this.props.onFocus) {
this.props.onFocus.call(this);
}
this.setState({ active: '', focus: true });
- };
+ }
- _handleQueryKeyDown = (event) => {
+ /**
+ * Handle query field key down.
+ * @param {object} event event.
+ */
+ _handleQueryKeyDown(event) {
event.stopPropagation();
const { which } = event;
const { active, options } = this.state;
@@ -188,16 +259,25 @@ class Autocomplete extends Component {
}
this.setState({ active: optionKeys[newIndex] });
}
- };
+ }
- _handleSuggestionHover = key => {
+ /**
+ * Handle suggestion hover.
+ * @param {number} key key.
+ */
+ _handleSuggestionHover(key) {
this.setState({ active: key });
- };
+ }
+ /**
+ * Handle selection of result.
+ * @param {number} key key.
+ */
_select(key) {
const { options } = this.state;
- const { onChange } = this.props;
- const resolvedLabel = options.get(key) || '';
+ const { onChange, labelName, wholeItem } = this.props;
+ const item = options.get(key) || {};
+ const resolvedLabel = item[labelName] || '';
this.refs.htmlInput.blur();
let newState = { inputValue: this.i18n(resolvedLabel), selected: key, focus: false };
if (this.props.onSelectClear === true) {
@@ -205,25 +285,30 @@ class Autocomplete extends Component {
}
this.setState(newState, () => {
if (onChange) {
- onChange(key);
+ onChange(wholeItem ? item : key);
}
});
}
- _renderOptions = () => {
+ /**
+ * Render options.
+ * @returns {JSXElement} options.
+ */
+ _renderOptions() {
+ const { labelName } = this.props;
const { active, options, focus } = this.state;
const renderedOptions = [];
- for (let [key, value] of options) {
+ for (let [key, item] of options) {
const isActive = active === key;
renderedOptions.push(
this._select(key)}
+ onMouseOver={() => this._handleSuggestionHover(key)}
>
- {this.i18n(value)}
+ {this.i18n(item[labelName])}
);
}
@@ -233,8 +318,9 @@ class Autocomplete extends Component {
{renderedOptions}
);
- };
+ }
+ /** @inheritdoc */
render() {
const { inputValue, isLoading } = this.state;
const { customError, renderOptions } = this.props;
@@ -270,8 +356,4 @@ class Autocomplete extends Component {
}
}
-Autocomplete.displayName = 'Autocomplete';
-Autocomplete.defaultProps = defaultProps;
-Autocomplete.propTypes = propTypes;
-
-export default Autocomplete;
+export default AutocompleteSelectEdit;
diff --git a/src/components/input/autocomplete-select/field.js b/src/components/input/autocomplete-select/field.js
index d6acb14d8..15d5b9822 100644
--- a/src/components/input/autocomplete-select/field.js
+++ b/src/components/input/autocomplete-select/field.js
@@ -1,44 +1,82 @@
import React, { Component, PropTypes } from 'react';
import AutocompleteSelectEdit from './edit';
import AutocompleteSelectConsult from './consult';
-import translation from 'focus-core/translation';
+import { translate } from 'focus-core/translation';
+/**
+ * Autocomplete select component.
+ */
class AutocompleteSelectField extends Component {
- state = {};
+ static displayName = 'AutocompleteSelectField';
+ /** Proptypes. */
static propTypes = {
isEdit: PropTypes.bool.isRequired,
keyResolver: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
querySearcher: PropTypes.func.isRequired
};
+ /** Initial state. */
+ state = {};
+
+ /**
+ * AutocompleteSelectField constructor.
+ * @param {object} props props.
+ */
+ constructor(props) {
+ super(props);
+
+ this._handleAutocompleteBadInput = this._handleAutocompleteBadInput.bind(this);
+ this._handleAutocompleteChange = this._handleAutocompleteChange.bind(this);
+ }
+
+ /** @inheritdoc */
componentWillReceiveProps({ error }) {
this.setState({ customError: error });
}
- getValue = () => {
+ /**
+ * Get value.
+ * @returns {string} value.
+ */
+ getValue() {
const { isEdit, value } = this.props;
if (isEdit) {
return this.refs.autocomplete.getValue();
} else {
return value;
}
- };
+ }
- _handleAutocompleteBadInput = value => {
- this.setState({ customError: translation.translate('autocomplete.error.badInput', { value }) })
- };
+ /**
+ * Handle bad input and display error message.
+ * @param {string} value value.
+ */
+ _handleAutocompleteBadInput(value) {
+ this.setState({ customError: translate('autocomplete.error.badInput', { value }) })
+ }
- _handleAutocompleteChange = value => {
+ /**
+ * Handle for value selection.
+ * @param {object} value value.
+ */
+ _handleAutocompleteChange(value) {
const { onChange } = this.props;
this.setState({ customError: null }, () => {
- if (onChange) onChange(value);
+ if (onChange) {
+ onChange(value);
+ }
});
- };
+ }
- _renderEdit = () => {
+ /**
+ * Renders component in edition mode.
+ * @returns {JSXElement} Edit component.
+ */
+ _renderEdit() {
const { customError } = this.state;
return (
);
- };
+ }
- _renderConsult = () => {
+ /**
+ * Renders component in consultation mode.
+ * @returns {JSXElement} Consult component.
+ */
+ _renderConsult() {
return (
);
- };
+ }
+ /** @inheritdoc */
render() {
const { isEdit } = this.props;
return isEdit ? this._renderEdit() : this._renderConsult();