diff --git a/src/components/input/autocomplete-select/consult.js b/src/components/input/autocomplete-select/consult.js index 79ecd4828..5a9491f5a 100644 --- a/src/components/input/autocomplete-select/consult.js +++ b/src/components/input/autocomplete-select/consult.js @@ -1,14 +1,34 @@ -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; import ComponentBaseBehaviour from '../../../behaviours/component-base'; +@ComponentBaseBehaviour /** * Autcomplete select component consultation view. */ -@ComponentBaseBehaviour 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 = {}; @@ -29,9 +49,10 @@ class AutocompleteSelectConsult extends Component { * @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); }); } @@ -39,8 +60,8 @@ 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(value); } document.addEventListener('click', this._handleDocumentClick); @@ -93,12 +99,11 @@ class AutocompleteSelectEdit extends Component { /** @inheritdoc */ componentWillReceiveProps({ value, customError, error }) { - const { keyResolver } = this.props; + const { labelName, wholeItem } = this.props; 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(value)); } else if (customError !== this.props.customError) { this.setState({ customError }); } @@ -126,22 +131,39 @@ class AutocompleteSelectEdit extends Component { document.removeEventListener('click', this._handleDocumentClick); } + /** + * Set value. + * @param {object|string} value Value. + */ + _setValue(value) { + const { keyResolver, labelName, wholeItem } = this.props; + + 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; } } @@ -162,7 +184,7 @@ class AutocompleteSelectEdit extends Component { }); } } - }; + } /** * Handle query change. @@ -172,28 +194,29 @@ class AutocompleteSelectEdit extends Component { 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); } - }; + } /** * Query searcher. * @param {string} value value. */ _querySearcher(value) { - const { querySearcher, keyName, labelName } = this.props; + 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 })); - }; + } /** * Handle query field focus. @@ -204,7 +227,7 @@ class AutocompleteSelectEdit extends Component { this.props.onFocus.call(this); } this.setState({ active: '', focus: true }); - }; + } /** * Handle query field key down. @@ -235,7 +258,7 @@ class AutocompleteSelectEdit extends Component { } this.setState({ active: optionKeys[newIndex] }); } - }; + } /** * Handle suggestion hover. @@ -243,7 +266,7 @@ class AutocompleteSelectEdit extends Component { */ _handleSuggestionHover(key) { this.setState({ active: key }); - }; + } /** * Handle selection of result. @@ -251,8 +274,9 @@ class AutocompleteSelectEdit extends Component { */ _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) { @@ -260,7 +284,7 @@ class AutocompleteSelectEdit extends Component { } this.setState(newState, () => { if (onChange) { - onChange(key); + onChange(wholeItem ? item : key); } }); } @@ -270,9 +294,10 @@ class AutocompleteSelectEdit extends Component { * @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])}
  • ); } @@ -292,7 +317,7 @@ class AutocompleteSelectEdit extends Component { {renderedOptions} ); - }; + } /** @inheritdoc */ render() { diff --git a/src/components/input/autocomplete-select/field.js b/src/components/input/autocomplete-select/field.js index 81602a0fb..15d5b9822 100644 --- a/src/components/input/autocomplete-select/field.js +++ b/src/components/input/autocomplete-select/field.js @@ -1,7 +1,7 @@ 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. @@ -15,6 +15,7 @@ class AutocompleteSelectField extends Component { isEdit: PropTypes.bool.isRequired, keyResolver: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, querySearcher: PropTypes.func.isRequired }; @@ -48,15 +49,15 @@ class AutocompleteSelectField extends Component { } else { return value; } - }; + } /** * Handle bad input and display error message. * @param {string} value value. */ _handleAutocompleteBadInput(value) { - this.setState({ customError: translation.translate('autocomplete.error.badInput', { value }) }) - }; + this.setState({ customError: translate('autocomplete.error.badInput', { value }) }) + } /** * Handle for value selection. @@ -69,10 +70,11 @@ class AutocompleteSelectField extends Component { onChange(value); } }); - }; + } /** * Renders component in edition mode. + * @returns {JSXElement} Edit component. */ _renderEdit() { const { customError } = this.state; @@ -85,10 +87,11 @@ class AutocompleteSelectField extends Component { {...this.props} /> ); - }; + } /** * Renders component in consultation mode. + * @returns {JSXElement} Consult component. */ _renderConsult() { return ( @@ -96,7 +99,7 @@ class AutocompleteSelectField extends Component { {...this.props} /> ); - }; + } /** @inheritdoc */ render() {