UNPKG

react-typeahead

Version:

React-based typeahead and typeahead-tokenizer

217 lines (198 loc) 6.97 kB
var Accessor = require('../accessor'); var React = require('react'); var Token = require('./token'); var KeyEvent = require('../keyevent'); var Typeahead = require('../typeahead'); var classNames = require('classnames'); var createReactClass = require('create-react-class'); var PropTypes = require('prop-types'); function _arraysAreDifferent(array1, array2) { if (array1.length != array2.length) { return true; } for (var i = array2.length - 1; i >= 0; i--) { if (array2[i] !== array1[i]) { return true; } } } /** * A typeahead that, when an option is selected, instead of simply filling * the text entry widget, prepends a renderable "token", that may be deleted * by pressing backspace on the beginning of the line with the keyboard. */ var TypeaheadTokenizer = createReactClass({ displayName: 'TypeaheadTokenizer', propTypes: { name: PropTypes.string, options: PropTypes.array, customClasses: PropTypes.object, allowCustomValues: PropTypes.number, defaultSelected: PropTypes.array, initialValue: PropTypes.string, placeholder: PropTypes.string, disabled: PropTypes.bool, inputProps: PropTypes.object, onTokenRemove: PropTypes.func, onKeyDown: PropTypes.func, onKeyPress: PropTypes.func, onKeyUp: PropTypes.func, onTokenAdd: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, filterOption: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), searchOptions: PropTypes.func, displayOption: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), formInputOption: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), maxVisible: PropTypes.number, resultsTruncatedMessage: PropTypes.string, defaultClassNames: PropTypes.bool, showOptionsWhenEmpty: PropTypes.bool }, getInitialState: function () { return { // We need to copy this to avoid incorrect sharing // of state across instances (e.g., via getDefaultProps()) selected: this.props.defaultSelected.slice(0) }; }, getDefaultProps: function () { return { options: [], defaultSelected: [], customClasses: {}, allowCustomValues: 0, initialValue: "", placeholder: "", disabled: false, inputProps: {}, defaultClassNames: true, filterOption: null, searchOptions: null, displayOption: function (token) { return token; }, formInputOption: null, onKeyDown: function (event) {}, onKeyPress: function (event) {}, onKeyUp: function (event) {}, onFocus: function (event) {}, onBlur: function (event) {}, onTokenAdd: function () {}, onTokenRemove: function () {}, showOptionsWhenEmpty: false }; }, componentWillReceiveProps: function (nextProps) { // if we get new defaultProps, update selected if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)) { this.setState({ selected: nextProps.defaultSelected.slice(0) }); } }, focus: function () { this.refs.typeahead.focus(); }, getSelectedTokens: function () { return this.state.selected; }, // TODO: Support initialized tokens // _renderTokens: function () { var tokenClasses = {}; tokenClasses[this.props.customClasses.token] = !!this.props.customClasses.token; var classList = classNames(tokenClasses); var result = this.state.selected.map(function (selected) { var displayString = Accessor.valueForOption(this.props.displayOption, selected); var value = Accessor.valueForOption(this.props.formInputOption || this.props.displayOption, selected); return React.createElement( Token, { key: displayString, className: classList, onRemove: this._removeTokenForValue, object: selected, value: value, name: this.props.name }, displayString ); }, this); return result; }, _getOptionsForTypeahead: function () { // return this.props.options without this.selected return this.props.options; }, _onKeyDown: function (event) { // We only care about intercepting backspaces if (event.keyCode === KeyEvent.DOM_VK_BACK_SPACE) { return this._handleBackspace(event); } this.props.onKeyDown(event); }, _handleBackspace: function (event) { // No tokens if (!this.state.selected.length) { return; } // Remove token ONLY when bksp pressed at beginning of line // without a selection var entry = this.refs.typeahead.refs.entry; if (entry.selectionStart == entry.selectionEnd && entry.selectionStart == 0) { this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]); event.preventDefault(); } }, _removeTokenForValue: function (value) { var index = this.state.selected.indexOf(value); if (index == -1) { return; } this.state.selected.splice(index, 1); this.setState({ selected: this.state.selected }); this.props.onTokenRemove(value); return; }, _addTokenForValue: function (value) { if (this.state.selected.indexOf(value) != -1) { return; } this.state.selected.push(value); this.setState({ selected: this.state.selected }); this.refs.typeahead.setEntryText(""); this.props.onTokenAdd(value); }, render: function () { var classes = {}; classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead; var classList = classNames(classes); var tokenizerClasses = [this.props.defaultClassNames && "typeahead-tokenizer"]; tokenizerClasses[this.props.className] = !!this.props.className; var tokenizerClassList = classNames(tokenizerClasses); return React.createElement( 'div', { className: tokenizerClassList }, this._renderTokens(), React.createElement(Typeahead, { ref: 'typeahead', className: classList, placeholder: this.props.placeholder, disabled: this.props.disabled, inputProps: this.props.inputProps, allowCustomValues: this.props.allowCustomValues, customClasses: this.props.customClasses, options: this._getOptionsForTypeahead(), initialValue: this.props.initialValue, maxVisible: this.props.maxVisible, resultsTruncatedMessage: this.props.resultsTruncatedMessage, onOptionSelected: this._addTokenForValue, onKeyDown: this._onKeyDown, onKeyPress: this.props.onKeyPress, onKeyUp: this.props.onKeyUp, onFocus: this.props.onFocus, onBlur: this.props.onBlur, displayOption: this.props.displayOption, defaultClassNames: this.props.defaultClassNames, filterOption: this.props.filterOption, searchOptions: this.props.searchOptions, showOptionsWhenEmpty: this.props.showOptionsWhenEmpty }) ); } }); module.exports = TypeaheadTokenizer;