Skip to content

Commit

Permalink
add a wholeItem option
Browse files Browse the repository at this point in the history
wholeItem change the value being managed by component from itemKey to item
  • Loading branch information
c3dr0x committed Sep 28, 2017
1 parent 5fef4f1 commit d587f40
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 54 deletions.
33 changes: 27 additions & 6 deletions src/components/input/autocomplete-select/consult.js
Original file line number Diff line number Diff line change
@@ -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 = {};

Expand All @@ -29,18 +49,19 @@ 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); });
}
}

/** @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 (
<div
Expand Down
107 changes: 66 additions & 41 deletions src/components/input/autocomplete-select/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ const TAB_KEY_CODE = 27;
const UP_ARROW_KEY_CODE = 38;
const DOWN_ARROW_KEY_CODE = 40;

/**
* Autcomplete select component edition view.
*/
@MDBehaviour('loader')
@MDBehaviour('inputText')
@ComponentBaseBehaviour
/**
* Autcomplete select component edition view.
*/
class AutocompleteSelectEdit extends Component {

/** DisplayName. */
Expand All @@ -26,35 +26,43 @@ class AutocompleteSelectEdit extends Component {
static propTypes = {
customError: PropTypes.string,
inputTimeout: PropTypes.number.isRequired,
keyName: PropTypes.string.isRequired,
keyName: PropTypes.string,
keyResolver: PropTypes.func.isRequired,
labelName: PropTypes.string.isRequired,
onBadInput: PropTypes.func,
labelName: PropTypes.string,
onBadInput: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
querySearcher: PropTypes.func.isRequired,
renderOptions: PropTypes.func,
value: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
onSelectClear: PropTypes.bool,
clearOnNullValue: PropTypes.bool
clearOnNullValue: PropTypes.bool,
wholeItem: PropTypes.bool
};

/** DefaultProps. */
static defaultProps = {
customError: null,
inputTimeout: 200,
keyName: 'key',
labelName: 'label',
inputTimeout: 200,
onFocus: undefined,
placeholder: '',
renderOptions: undefined,
value: null,
onSelectClear: false,
clearOnNullValue: true
clearOnNullValue: true,
wholeItem: false
};

/** Initial state. */
state = {
focus: false,
inputValue: this.props.value,
inputValue: this.props.wholeItem ? this.props.value[this.props.labelName] : this.props.value,
options: new Map(),
active: null,
selected: this.props.value,
selected: this.props.wholeItem ? this.props.value[this.props.keyName] : this.props.value,
fromKeyResolver: false,
isLoading: false,
customError: this.props.customError,
Expand All @@ -79,12 +87,10 @@ class AutocompleteSelectEdit extends Component {

/** @inheritdoc */
componentDidMount() {
const { value, keyResolver, inputTimeout } = this.props;
const { value, inputTimeout } = this.props;

if (value !== undefined && value !== null) { // value is defined, call the keyResolver to get the associated label
keyResolver(value).then(inputValue => {
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);
Expand All @@ -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 });
}
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -162,7 +184,7 @@ class AutocompleteSelectEdit extends Component {
});
}
}
};
}

/**
* Handle query change.
Expand All @@ -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.
Expand All @@ -204,7 +227,7 @@ class AutocompleteSelectEdit extends Component {
this.props.onFocus.call(this);
}
this.setState({ active: '', focus: true });
};
}

/**
* Handle query field key down.
Expand Down Expand Up @@ -235,32 +258,33 @@ class AutocompleteSelectEdit extends Component {
}
this.setState({ active: optionKeys[newIndex] });
}
};
}

/**
* 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) {
newState = { inputValue: '', selected: null, focus: false };
}
this.setState(newState, () => {
if (onChange) {
onChange(key);
onChange(wholeItem ? item : key);
}
});
}
Expand All @@ -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(
<li
Expand All @@ -282,7 +307,7 @@ class AutocompleteSelectEdit extends Component {
onClick={() => this._select(key)}
onMouseOver={() => this._handleSuggestionHover(key)}
>
{this.i18n(value)}
{this.i18n(item[labelName])}
</li>
);
}
Expand All @@ -292,7 +317,7 @@ class AutocompleteSelectEdit extends Component {
{renderedOptions}
</ul>
);
};
}

/** @inheritdoc */
render() {
Expand Down
17 changes: 10 additions & 7 deletions src/components/input/autocomplete-select/field.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
};

Expand Down Expand Up @@ -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.
Expand All @@ -69,10 +70,11 @@ class AutocompleteSelectField extends Component {
onChange(value);
}
});
};
}

/**
* Renders component in edition mode.
* @returns {JSXElement} Edit component.
*/
_renderEdit() {
const { customError } = this.state;
Expand All @@ -85,18 +87,19 @@ class AutocompleteSelectField extends Component {
{...this.props}
/>
);
};
}

/**
* Renders component in consultation mode.
* @returns {JSXElement} Consult component.
*/
_renderConsult() {
return (
<AutocompleteSelectConsult
{...this.props}
/>
);
};
}

/** @inheritdoc */
render() {
Expand Down

0 comments on commit d587f40

Please sign in to comment.