From 0db4bea6ad1aefc12f0981bbef10e486e2824203 Mon Sep 17 00:00:00 2001 From: c3dr0x Date: Thu, 28 Sep 2017 11:57:53 +0200 Subject: [PATCH 1/3] add comments and bind properly methods --- .../input/autocomplete-select/consult.js | 13 ++ .../input/autocomplete-select/edit.js | 158 ++++++++++++------ .../input/autocomplete-select/field.js | 54 +++++- 3 files changed, 166 insertions(+), 59 deletions(-) diff --git a/src/components/input/autocomplete-select/consult.js b/src/components/input/autocomplete-select/consult.js index a9e3389bd..15e18994b 100644 --- a/src/components/input/autocomplete-select/consult.js +++ b/src/components/input/autocomplete-select/consult.js @@ -1,21 +1,33 @@ import React, { Component } from 'react'; import ComponentBaseBehaviour from '../../../behaviours/component-base'; +/** + * Autcomplete select component consultation view. + */ @ComponentBaseBehaviour class AutocompleteSelectConsult extends Component { + static displayName = 'AutocompleteSelectConsult'; + + /** 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; if (keyResolver && value !== undefined && value !== null) { @@ -27,6 +39,7 @@ class AutocompleteSelectConsult extends Component { } } + /** @inheritdoc */ render() { const { label, name, type, value } = this.props; const { resolvedLabel = value } = this.state; diff --git a/src/components/input/autocomplete-select/edit.js b/src/components/input/autocomplete-select/edit.js index 5532946ed..f27c1fa1f 100644 --- a/src/components/input/autocomplete-select/edit.js +++ b/src/components/input/autocomplete-select/edit.js @@ -11,54 +11,73 @@ const TAB_KEY_CODE = 27; const UP_ARROW_KEY_CODE = 38; const DOWN_ARROW_KEY_CODE = 40; -const propTypes = { - customError: PropTypes.string, - inputTimeout: PropTypes.number.isRequired, - keyName: PropTypes.string.isRequired, - keyResolver: PropTypes.func.isRequired, - labelName: PropTypes.string.isRequired, - onBadInput: PropTypes.func, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.string, - querySearcher: PropTypes.func.isRequired, - renderOptions: PropTypes.func, - value: PropTypes.string, - onSelectClear: PropTypes.bool, - clearOnNullValue: PropTypes.bool -}; - -const defaultProps = { - keyName: 'key', - labelName: 'label', - inputTimeout: 200, - onSelectClear: false, - clearOnNullValue: true -}; - +/** + * Autcomplete select component edition view. + */ @MDBehaviour('loader') @MDBehaviour('inputText') @ComponentBaseBehaviour -class Autocomplete extends Component { +class AutocompleteSelectEdit extends Component { + + /** DisplayName. */ + static displayName = 'AutocompleteSelectEdit'; + + /** PropTypes. */ + static propTypes = { + customError: PropTypes.string, + inputTimeout: PropTypes.number.isRequired, + keyName: PropTypes.string.isRequired, + keyResolver: PropTypes.func.isRequired, + labelName: PropTypes.string.isRequired, + onBadInput: PropTypes.func, + onChange: PropTypes.func.isRequired, + placeholder: PropTypes.string, + querySearcher: PropTypes.func.isRequired, + renderOptions: PropTypes.func, + value: PropTypes.string, + onSelectClear: PropTypes.bool, + clearOnNullValue: PropTypes.bool + }; + + /** DefaultProps. */ + static defaultProps = { + keyName: 'key', + labelName: 'label', + inputTimeout: 200, + onSelectClear: false, + clearOnNullValue: true + }; + /** Initial state. */ + state = { + focus: false, + inputValue: this.props.value, + options: new Map(), + active: null, + selected: this.props.value, + fromKeyResolver: false, + isLoading: false, + customError: this.props.customError, + totalCount: 0 + }; + + /** + * AutocompleteSelectEdit constructor. + * @param {object} props props. + */ constructor(props) { super(props); - const state = { - focus: false, - inputValue: this.props.value, - options: new Map(), - active: null, - selected: this.props.value, - fromKeyResolver: false, - isLoading: false, - customError: this.props.customError, - totalCount: 0 - }; - - this.state = state; this.autocompleteId = uniqueId('autocomplete-text-'); + + this._handleQueryChange = this._handleQueryChange.bind(this); + this._handleQueryFocus = this._handleQueryFocus.bind(this); + this._handleQueryKeyDown = this._handleQueryKeyDown.bind(this); + this._querySearcher = this._querySearcher.bind(this); + this._handleDocumentClick = this._handleDocumentClick.bind(this); } + /** @inheritdoc */ componentDidMount() { const { value, keyResolver, inputTimeout } = this.props; @@ -72,6 +91,7 @@ class Autocomplete extends Component { this._debouncedQuerySearcher = debounce(this._querySearcher, inputTimeout); } + /** @inheritdoc */ componentWillReceiveProps({ value, customError, error, keyResolver }) { 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 => { @@ -90,6 +110,7 @@ class Autocomplete extends Component { } } + /** @inheritdoc */ componentDidUpdate() { if (this.props.customError) { this.refs.inputText.classList.add('is-invalid'); @@ -98,10 +119,15 @@ class Autocomplete extends Component { } } + /** @inheritdoc */ componentWillUnmount() { document.removeEventListener('click', this._handleDocumentClick); } + /** + * Get value. + * @returns {string} value. + */ getValue() { const { labelName, keyName, value } = this.props; const { inputValue, selected, options, fromKeyResolver } = this.state; @@ -117,7 +143,11 @@ class Autocomplete extends Component { } } - _handleDocumentClick = ({ target }) => { + /** + * Handle document click. + * @param {object} event event. + */ + _handleDocumentClick({ target }) { const { focus, inputValue } = this.state; const { onBadInput } = this.props; if (focus) { @@ -132,7 +162,11 @@ 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 }); @@ -143,7 +177,11 @@ class Autocomplete extends Component { } }; - _querySearcher = value => { + /** + * Query searcher. + * @param {string} value value. + */ + _querySearcher(value) { const { querySearcher, keyName, labelName } = this.props; querySearcher(value).then(({ data, totalCount }) => { // TODO handle the incomplete option list case @@ -155,7 +193,10 @@ class Autocomplete extends Component { }).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); @@ -163,7 +204,11 @@ class Autocomplete extends Component { 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; @@ -190,10 +235,18 @@ class Autocomplete extends Component { } }; - _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; @@ -210,7 +263,11 @@ class Autocomplete extends Component { }); } - _renderOptions = () => { + /** + * Render options. + * @returns {JSXElement} options. + */ + _renderOptions() { const { active, options, focus } = this.state; const renderedOptions = []; for (let [key, value] of options) { @@ -220,8 +277,8 @@ class Autocomplete extends Component { data-active={isActive} data-focus='option' key={key} - onClick={this._select.bind(this, key)} - onMouseOver={this._handleSuggestionHover.bind(this, key)} + onClick={() => this._select(key)} + onMouseOver={() => this._handleSuggestionHover(key)} > {this.i18n(value)} @@ -235,6 +292,7 @@ class Autocomplete extends Component { ); }; + /** @inheritdoc */ render() { const { inputValue, isLoading } = this.state; const { customError, renderOptions } = this.props; @@ -270,8 +328,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..81602a0fb 100644 --- a/src/components/input/autocomplete-select/field.js +++ b/src/components/input/autocomplete-select/field.js @@ -3,10 +3,14 @@ import AutocompleteSelectEdit from './edit'; import AutocompleteSelectConsult from './consult'; import translation 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, @@ -14,11 +18,30 @@ class AutocompleteSelectField extends Component { 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(); @@ -27,18 +50,31 @@ class AutocompleteSelectField extends Component { } }; - _handleAutocompleteBadInput = value => { + /** + * Handle bad input and display error message. + * @param {string} value value. + */ + _handleAutocompleteBadInput(value) { this.setState({ customError: translation.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. + */ + _renderEdit() { const { customError } = this.state; return ( { + /** + * Renders component in consultation mode. + */ + _renderConsult() { return ( Date: Thu, 28 Sep 2017 13:34:43 +0200 Subject: [PATCH 2/3] add a wholeItem option wholeItem change the value being managed by component from itemKey to item --- .../input/autocomplete-select/consult.js | 33 +++++- .../input/autocomplete-select/edit.js | 105 +++++++++++------- .../input/autocomplete-select/field.js | 17 +-- 3 files changed, 102 insertions(+), 53 deletions(-) diff --git a/src/components/input/autocomplete-select/consult.js b/src/components/input/autocomplete-select/consult.js index 15e18994b..805985020 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); }); } else { @@ -41,8 +62,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); @@ -94,9 +100,8 @@ class AutocompleteSelectEdit extends Component { /** @inheritdoc */ componentWillReceiveProps({ value, customError, error, keyResolver }) { 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 }); } @@ -124,22 +129,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; } } @@ -160,7 +182,7 @@ class AutocompleteSelectEdit extends Component { }); } } - }; + } /** * Handle query change. @@ -170,28 +192,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. @@ -202,7 +225,7 @@ class AutocompleteSelectEdit extends Component { this.props.onFocus.call(this); } this.setState({ active: '', focus: true }); - }; + } /** * Handle query field key down. @@ -233,7 +256,7 @@ class AutocompleteSelectEdit extends Component { } this.setState({ active: optionKeys[newIndex] }); } - }; + } /** * Handle suggestion hover. @@ -241,7 +264,7 @@ class AutocompleteSelectEdit extends Component { */ _handleSuggestionHover(key) { this.setState({ active: key }); - }; + } /** * Handle selection of result. @@ -249,8 +272,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) { @@ -258,7 +282,7 @@ class AutocompleteSelectEdit extends Component { } this.setState(newState, () => { if (onChange) { - onChange(key); + onChange(wholeItem ? item : key); } }); } @@ -268,9 +292,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])}
  • ); } @@ -290,7 +315,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() { From 9128e3dadb2f8a4d2ff439d9feac0e94dd927070 Mon Sep 17 00:00:00 2001 From: Hartorn Date: Sun, 8 Oct 2017 21:27:28 +0200 Subject: [PATCH 3/3] AutocompleteSelectEdit - Fixing merge issue + changing _setValue function --- src/components/input/autocomplete-select/edit.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/input/autocomplete-select/edit.js b/src/components/input/autocomplete-select/edit.js index 0790a6248..1af20919d 100644 --- a/src/components/input/autocomplete-select/edit.js +++ b/src/components/input/autocomplete-select/edit.js @@ -90,7 +90,7 @@ class AutocompleteSelectEdit extends Component { const { value, inputTimeout } = this.props; if (value !== undefined && value !== null) { - this._setValue(value); + this._setValue(this.props); } document.addEventListener('click', this._handleDocumentClick); @@ -98,10 +98,11 @@ class AutocompleteSelectEdit extends Component { } /** @inheritdoc */ - componentWillReceiveProps({ value, customError, error, keyResolver }) { + 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 const inputValue = wholeItem ? value[labelName] : value; - this.setState({ inputValue, customError }, () => this._setValue(value)); + this.setState({ inputValue, customError }, () => this._setValue(nextProps)); } else if (customError !== this.props.customError) { this.setState({ customError }); } @@ -129,12 +130,14 @@ class AutocompleteSelectEdit extends Component { document.removeEventListener('click', this._handleDocumentClick); } + /** * Set value. - * @param {object|string} value Value. + * + * @param {object} object containing props + * @memberof AutocompleteSelectEdit */ - _setValue(value) { - const { keyResolver, labelName, wholeItem } = this.props; + _setValue({ value, keyResolver, labelName, wholeItem }) { if (wholeItem) { this.setState({ inputValue: value[labelName], fromKeyResolver: true });