UNPKG

ssc-refer

Version:
838 lines (756 loc) 26.3 kB
'use strict'; import _extends from 'babel-runtime/helpers/extends'; import _JSON$stringify from 'babel-runtime/core-js/json/stringify'; import cx from 'classnames'; import { find, isEqual, noop, throttle, forEach, isArray } from 'lodash'; import onClickOutside from 'react-onclickoutside'; import React from 'react'; import PropTypes from 'prop-types'; import ClearButton from './ClearButton.react'; import Loader from './Loader.react'; import Overlay from './Overlay.react'; import TokenizerInput from './TokenizerInput.react'; import TypeaheadInput from './TypeaheadInput.react'; import TypeaheadMenu from './TypeaheadMenu.react'; import ReferList from './ReferList.react'; import ReferTable from './ReferTable.react'; import ReferTreeTable from './ReferTreeTable.react'; import addCustomOption from './utils/addCustomOption'; import defaultFilterBy from './utils/defaultFilterBy'; import getHintText from './utils/getHintText'; import getInputText from './utils/getInputText'; import getOptionLabel from './utils/getOptionLabel'; import getTruncatedOptions from './utils/getTruncatedOptions'; import warn from './utils/warn'; import request from 'superagent'; import { DOWN, ESC, RETURN, TAB, UP } from './utils/keyCode'; /** * Refer */ var Refers = React.createClass({ displayName: 'Refers', propTypes: { /** * Allows the creation of new selections on the fly. Note that any new items * will be added to the list of selections, but not the list of original * options unless handled as such by `Typeahead`'s parent. */ allowNew: PropTypes.bool, /** * Autofocus the input when the component initially mounts. */ autoFocus: PropTypes.bool, /** * Whether to render the menu inline or attach to `document.body`. */ bodyContainer: PropTypes.bool, /** * Whether or not filtering should be case-sensitive. */ caseSensitive: PropTypes.bool, /** * Displays a button to clear the input when there are selections. */ clearButton: PropTypes.bool, /** * Specify any pre-selected options. Use only if you want the component to * be uncontrolled. */ defaultSelected: PropTypes.array, /** * Specify whether the menu should appear above the input. */ dropup: PropTypes.bool, /** * Either an array of fields in `option` to search, or a custom filtering * callback. */ filterBy: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string.isRequired), PropTypes.func]), /** * Whether the filter should ignore accents and other diacritical marks. */ ignoreDiacritics: PropTypes.bool, /** * Indicate whether an asynchromous data fetch is happening. */ /** * Indicate whether an asynchromous data fetch is happening. */ isLoading: PropTypes.bool, /** * Specify the option key to use for display or a function returning the * display string. By default, the selector will use the `label` key. */ labelKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), /** * Maximum number of results to display by default. Mostly done for * performance reasons so as not to render too many DOM nodes in the case of * large data sets. */ maxResults: PropTypes.number, /** * Number of input characters that must be entered before showing results. */ minLength: PropTypes.number, /** * Whether or not multiple selections are allowed. */ multiple: PropTypes.bool, /** * Callback fired when the input is blurred. Receives an event. */ onBlur: PropTypes.func, /** * Callback fired whenever items are added or removed. Receives an array of * the selected options. */ onChange: PropTypes.func, /** * Callback fired when the input is focused. Receives an event. */ onFocus: PropTypes.func, /** * Callback for handling changes to the user-input text. */ onInputChange: PropTypes.func, /** * Give user the ability to display additional results if the number of * results exceeds `maxResults`. */ paginate: PropTypes.bool, /** * Callback for custom menu rendering. */ renderMenu: PropTypes.func, /** * The selected option(s) displayed in the input. Use this prop if you want * to control the component via its parent. */ selected: PropTypes.array, /** * set refer data url ,for example `http://YOURHOST/queryRefJSON` */ referDataUrl: PropTypes.string.isRequired, /** * set refOptions ,for example `{"refCode":"dept","refType":"tree","rootName":"部门"}` */ referConditions: PropTypes.object.isRequired, /** * Is debug mode. */ requestHeader: PropTypes.object, /** * set refer type, for example: list, cascader, table, treetable, default type is list. */ referType: PropTypes.string.isRequired, /** * set custom columns for table display, for example `[{"field":"name", "label":"名称"},{"field":"code","label":"编码"},{"field":"addr","label":"地址"}]` */ tableColumns: PropTypes.array, /** * set refer whether to display the Disabled button */ showDisabledBtn: PropTypes.bool, /** * 显示停用按钮名称,默认值:'显示停用' */ showDisabledBtnText: PropTypes.string, /** * 隐藏停用按钮名称,默认值:'隐藏停用' */ showDisabledBtnText_Not: PropTypes.string, /** * set refer whether to display Disabled data */ showDisabled: PropTypes.bool, /** * Is debug mode. */ debugMode: PropTypes.bool, /** * search icon style */ searchIcon: PropTypes.string }, getDefaultProps: function getDefaultProps() { return { allowNew: false, autoFocus: false, bodyContainer: false, caseSensitive: false, clearButton: false, defaultSelected: [], dropup: false, filterBy: [], ignoreDiacritics: true, isLoading: false, labelKey: 'label', maxResults: 100, minLength: 0, multiple: false, onBlur: noop, onChange: noop, onFocus: noop, onInputChange: noop, paginate: true, selected: [], referDataUrl: 'http://172.20.4.220/ficloud/refbase_ctr/queryRefJSON', referConditions: {}, requestHeader: {}, referType: 'list', debugMode: false, showDisabledBtn: false, showDisabledBtnText: '显示停用', showDisabledBtnText_Not: '隐藏停用', showDisabled: false, searchIcon: 'glyphicon glyphicon-search refer-search-icon' }; }, childContextTypes: { activeIndex: PropTypes.number.isRequired, onActiveItemChange: PropTypes.func.isRequired, onInitialItemChange: PropTypes.func.isRequired, onMenuItemClick: PropTypes.func.isRequired }, getChildContext: function getChildContext() { return { activeIndex: this.state.activeIndex, onActiveItemChange: this._handleActiveItemChange, onInitialItemChange: this._handleInitialItemChange, onMenuItemClick: this._handleAddOption }; }, getInitialState: function getInitialState() { var _props = this.props, defaultSelected = _props.defaultSelected, maxResults = _props.maxResults, showDisabled = _props.showDisabled; var selected = this.props.selected.slice(); if (defaultSelected && defaultSelected.length) { selected = defaultSelected; } return { activeIndex: -1, activeItem: null, initialItem: null, selected: selected, showMenu: false, shownResults: maxResults, text: '', isAbove: true, responseData: [], styleStatus: { position: 'relative' }, showDisabled: showDisabled === undefined ? false : showDisabled }; }, componentWillMount: function componentWillMount() { var _props2 = this.props, allowNew = _props2.allowNew, labelKey = _props2.labelKey; warn(!(typeof labelKey === 'function' && allowNew), '`labelKey` must be a string if creating new options is allowed.'); }, componentDidMount: function componentDidMount() { this.props.autoFocus && this.focus(); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { var multiple = nextProps.multiple, selected = nextProps.selected; if (!isEqual(selected, this.props.selected)) { // If new selections are passed in via props, treat the component as a // controlled input. this.setState({ selected: selected }); } if (multiple !== this.props.multiple) { this.setState({ text: '' }); } }, render: function render() { var _props3 = this.props, allowNew = _props3.allowNew, className = _props3.className, dropup = _props3.dropup, labelKey = _props3.labelKey, paginate = _props3.paginate, searchIcon = _props3.searchIcon; var _state = this.state, shownResults = _state.shownResults, text = _state.text; // First filter the results by the input string. var results = this._getFilteredResults(); // This must come before we truncate. var shouldPaginate = paginate && results.length > shownResults; // Truncate if necessary. if (shouldPaginate) { results = getTruncatedOptions(results, shownResults); } // Add the custom option. if (allowNew) { results = addCustomOption(results, text, labelKey); } return React.createElement( 'div', { className: cx('bootstrap-typeahead', 'open', { 'dropup': dropup }, className), style: this.state.styleStatus }, this._renderInput(results), this._renderAux(), this._renderMenu(results, shouldPaginate) ); }, _getFilteredResults: function _getFilteredResults() { var _props4 = this.props, caseSensitive = _props4.caseSensitive, filterBy = _props4.filterBy, ignoreDiacritics = _props4.ignoreDiacritics, labelKey = _props4.labelKey, minLength = _props4.minLength, multiple = _props4.multiple; var _state2 = this.state, selected = _state2.selected, text = _state2.text; if (text.length < minLength) { return []; } var callback = Array.isArray(filterBy) ? function (option) { return defaultFilterBy(option, text, labelKey, multiple && !!find(selected, function (o) { return isEqual(o, option); }), { caseSensitive: caseSensitive, ignoreDiacritics: ignoreDiacritics, fields: filterBy }); } : function (option) { return filterBy(option, text); }; return this.state.responseData.filter(callback); }, getFilteredSelected: function getFilteredSelected(responseData, selectedData) { var result = void 0; result = responseData.filter(function (item) { var flag = false; selectedData.map(function (obj) { if (_JSON$stringify(obj) === _JSON$stringify(item)) flag = true; }); return !flag; }); return result; }, _loadData: function _loadData() { var _props5 = this.props, referDataUrl = _props5.referDataUrl, referConditions = _props5.referConditions, requestHeader = _props5.requestHeader, debugMode = _props5.debugMode, selected = _props5.selected; var _this = this; referConditions['disableshow'] = _this.state.showDisabled; request.post(referDataUrl).set(requestHeader).set('Content-Type', 'application/json').send(_JSON$stringify(referConditions)).end(function (err, res) { if (err || !res.ok) { if (debugMode) console.log('network error!' + err); } else { var data = JSON.parse(res.text); if (data['success'] === undefined) { if (debugMode) console.log('response data format is error,for example: no success key'); return false; } if (!data['success']) { if (debugMode) console.log('response data success is false' + data['message']); } else { if (isArray(data.data) && data.data.length >= 0) { _this.setState({ responseData: _this.getFilteredSelected(data.data, selected) }); } else { if (debugMode) console.log('Message:' + 'Data format error, maybe no data !'); } } } }); }, blur: function blur() { this.refs.input.blur(); this._hideDropdown(); }, /** * Public method to allow external clearing of the input. Clears both text * and selection(s). */ clear: function clear() { var _getInitialState = this.getInitialState(), activeIndex = _getInitialState.activeIndex, activeItem = _getInitialState.activeItem, showMenu = _getInitialState.showMenu; var selected = []; var text = ''; this.setState({ activeIndex: activeIndex, activeItem: activeItem, selected: selected, showMenu: showMenu, text: text }); this.props.onChange(selected); this.props.onInputChange(text); }, focus: function focus() { this.refs.input.focus(); }, getData: function getData() { return this.state.selected; }, getInputTextValue: function getInputTextValue() { var _props6 = this.props, bsSize = _props6.bsSize, disabled = _props6.disabled, labelKey = _props6.labelKey, minLength = _props6.minLength, multiple = _props6.multiple, name = _props6.name, placeholder = _props6.placeholder, renderToken = _props6.renderToken; var _state3 = this.state, activeIndex = _state3.activeIndex, activeItem = _state3.activeItem, initialItem = _state3.initialItem, selected = _state3.selected, text = _state3.text; return getInputText({ activeItem: activeItem, labelKey: labelKey, multiple: multiple, selected: selected, text: text }); }, hideRefers: function hideRefers() { this.setState({ styleStatus: { display: 'none' } }); }, showRefers: function showRefers() { this.setState({ styleStatus: { position: 'relative' } }); }, _renderInput: function _renderInput(results) { var _this2 = this; var _props7 = this.props, bsSize = _props7.bsSize, disabled = _props7.disabled, labelKey = _props7.labelKey, minLength = _props7.minLength, multiple = _props7.multiple, name = _props7.name, placeholder = _props7.placeholder, renderToken = _props7.renderToken, searchIcon = _props7.searchIcon, showDisabledBtn = _props7.showDisabledBtn, showDisabledBtnText = _props7.showDisabledBtnText, showDisabledBtnText_Not = _props7.showDisabledBtnText_Not; var _state4 = this.state, activeIndex = _state4.activeIndex, activeItem = _state4.activeItem, initialItem = _state4.initialItem, selected = _state4.selected, text = _state4.text; var Input = multiple ? TokenizerInput : TypeaheadInput; var inputProps = { bsSize: bsSize, disabled: disabled, name: name, placeholder: placeholder, renderToken: renderToken }; return React.createElement( 'div', { className: 'input-group' }, React.createElement(Input, _extends({}, inputProps, { activeIndex: activeIndex, activeItem: activeItem, hasAux: !!this._renderAux(), hintText: getHintText({ activeItem: activeItem, initialItem: initialItem, labelKey: labelKey, minLength: minLength, selected: selected, text: text }), initialItem: initialItem, labelKey: labelKey, onAdd: this._handleAddOption, onBlur: this._handleBlur, onChange: this._handleTextChange, onFocus: this._handleFocus.bind(this, disabled), onKeyDown: function onKeyDown(e) { return _this2._handleKeydown(results, e); }, onRemove: this._handleRemoveOption, options: results, ref: 'input', selected: selected.slice(), value: getInputText({ activeItem: activeItem, labelKey: labelKey, multiple: multiple, selected: selected, text: text }) })), React.createElement( 'span', { className: "input-group-addon cursor-style", onClick: this._handleFocus.bind(this, disabled), style: disabled ? { 'cursor': 'no-drop' } : {} }, React.createElement('span', { className: cx(searchIcon) }) ), showDisabledBtn === false ? null : React.createElement( 'span', { className: 'input-group-addon cursor-style', style: disabled ? { 'cursor': 'no-drop' } : {}, title: this.state.showDisabled ? showDisabledBtnText_Not || '隐藏停用' : showDisabledBtnText || '显示停用', onClick: this._handleEnable.bind(this, disabled) }, React.createElement('span', { className: this.state.showDisabled ? 'icon-show-disabled red' : 'icon-show-disabled' }) ) ); }, _renderMenu: function _renderMenu(results, shouldPaginate) { var _this3 = this; var _props8 = this.props, align = _props8.align, bodyContainer = _props8.bodyContainer, dropup = _props8.dropup, emptyLabel = _props8.emptyLabel, labelKey = _props8.labelKey, maxHeight = _props8.maxHeight, minLength = _props8.minLength, newSelectionPrefix = _props8.newSelectionPrefix, paginationText = _props8.paginationText, renderMenu = _props8.renderMenu, renderMenuItemChildren = _props8.renderMenuItemChildren, referType = _props8.referType, tableColumns = _props8.tableColumns; var _state5 = this.state, showMenu = _state5.showMenu, text = _state5.text; var menuProps = { align: align, dropup: dropup, emptyLabel: emptyLabel, labelKey: labelKey, maxHeight: maxHeight, newSelectionPrefix: newSelectionPrefix, paginationText: paginationText, onPaginate: this._handlePagination, paginate: shouldPaginate, text: text, tableColumns: tableColumns }; var list = renderMenu ? renderMenu(results, menuProps) : React.createElement(TypeaheadMenu, _extends({}, menuProps, { options: results, renderMenuItemChildren: renderMenuItemChildren })); var typeObj = noop; switch (referType) { case 'list': typeObj = list; break; case 'cascader': var cascader = renderMenu ? renderMenu(results, menuProps) : React.createElement(ReferList, _extends({}, menuProps, { options: results, renderMenuItemChildren: renderMenuItemChildren })); typeObj = cascader; break; case 'table': var table = renderMenu ? renderMenu(results, menuProps) : React.createElement(ReferTable, _extends({}, menuProps, { options: results, onClickItem: this._handleAddOption })); typeObj = table; break; case 'treetable': var treetable = renderMenu ? renderMenu(results, menuProps) : React.createElement(ReferTreeTable, _extends({}, menuProps, { options: results, onClickItem: this._handleAddOption })); typeObj = treetable; break; default: typeObj = list; } return React.createElement( Overlay, { container: bodyContainer ? document.body : this, show: showMenu && text.length >= minLength, target: function target() { return _this3.refs.input; } }, typeObj ); }, _renderAux: function _renderAux() { var _props9 = this.props, bsSize = _props9.bsSize, clearButton = _props9.clearButton, disabled = _props9.disabled, isLoading = _props9.isLoading; if (isLoading) { return React.createElement(Loader, { bsSize: bsSize }); } if (clearButton && !disabled && this.state.selected.length) { return React.createElement(ClearButton, { bsSize: bsSize, className: 'bootstrap-typeahead-clear-button', onClick: this.clear }); } }, _handleActiveItemChange: function _handleActiveItemChange(activeItem) { this.setState({ activeItem: activeItem }); }, _handleBlur: function _handleBlur(e) { // Note: Don't hide the menu here, since that interferes with other actions // like making a selection by clicking on a menu item. if (this.props.onBlur) { this.props.onBlur(e); } }, _handleFocus: function _handleFocus(disabled, e) { if (disabled) { return; } this.props.onFocus(e); var multiple = this.props.multiple; if (!multiple) { // this.clear(); } this._loadData(); this.setState({ showMenu: true }); }, _handleEnable: function _handleEnable(disabled, e) { if (disabled) { return; } this._handleBlur(e); var showDisabled = this.state.showDisabled === undefined ? this.getInitialState() : this.state.showDisabled; this.setState({ showDisabled: !showDisabled, showMenu: false }); }, _handleInitialItemChange: function _handleInitialItemChange(initialItem) { var currentItem = this.state.initialItem; if (!currentItem) { this.setState({ initialItem: initialItem }); return; } var labelKey = this.props.labelKey; // Don't update the initial item if it hasn't changed. For custom items, // compare the `labelKey` values since a unique id is generated each time, // causing the comparison to always return false otherwise. if (isEqual(initialItem, currentItem) || initialItem.customOption && initialItem[labelKey] === currentItem[labelKey]) { return; } this.setState({ initialItem: initialItem }); }, _handleTextChange: function _handleTextChange(text) { var _getInitialState2 = this.getInitialState(), activeIndex = _getInitialState2.activeIndex, activeItem = _getInitialState2.activeItem; this.setState({ activeIndex: activeIndex, activeItem: activeItem, showMenu: true, text: text }); this.props.onInputChange(text); }, _handleChange: function _handleChange(text, e) { this._handleTextChange(text); this.props.onChange(e); }, _handleKeydown: function _handleKeydown(options, e) { var _state6 = this.state, activeItem = _state6.activeItem, showMenu = _state6.showMenu; switch (e.keyCode) { case UP: case DOWN: // Don't cycle through the options if the menu is hidden. if (!showMenu) { return; } var activeIndex = this.state.activeIndex; // Prevents input cursor from going to the beginning when pressing up. e.preventDefault(); // Increment or decrement index based on user keystroke. activeIndex += e.keyCode === UP ? -1 : 1; // If we've reached the end, go back to the beginning or vice-versa. if (activeIndex === options.length) { activeIndex = -1; } else if (activeIndex === -2) { activeIndex = options.length - 1; } var newState = { activeIndex: activeIndex }; if (activeIndex === -1) { // Reset the active item if there is no active index. newState.activeItem = null; } this.setState(newState); break; case ESC: case TAB: // Prevent closing dialogs. e.keyCode === ESC && e.preventDefault(); this._hideDropdown(); break; case RETURN: // Prevent submitting forms. e.preventDefault(); if (showMenu) { activeItem && this._handleAddOption(activeItem); } break; } }, _handleAddOption: function _handleAddOption(selectedOption) { var _props10 = this.props, multiple = _props10.multiple, labelKey = _props10.labelKey, onChange = _props10.onChange, onInputChange = _props10.onInputChange; var selected = void 0; var text = void 0; if (multiple) { // If multiple selections are allowed, add the new selection to the // existing selections. selected = this.state.selected.concat(selectedOption); text = ''; } else { // If only a single selection is allowed, replace the existing selection // with the new one. selected = [selectedOption]; text = getOptionLabel(selectedOption, labelKey); } this.setState({ initialItem: selectedOption, selected: selected, text: text }); this._hideDropdown(); onChange(selected); onInputChange(text); }, _handlePagination: function _handlePagination(e) { var shownResults = this.state.shownResults + this.props.maxResults; // Keep the input focused when paginating. this.focus(); this.setState({ shownResults: shownResults }); }, _handleRemoveOption: function _handleRemoveOption(removedOption) { var selected = this.state.selected.slice(); selected = selected.filter(function (option) { return !isEqual(option, removedOption); }); // Make sure the input stays focused after the item is removed. this.focus(); this.setState({ selected: selected }); this._hideDropdown(); this.props.onChange(selected); }, /** * From `onClickOutside` HOC. */ handleClickOutside: function handleClickOutside(e) { this.state.showMenu && this._hideDropdown(); }, _hideDropdown: function _hideDropdown() { var _getInitialState3 = this.getInitialState(), activeIndex = _getInitialState3.activeIndex, activeItem = _getInitialState3.activeItem, showMenu = _getInitialState3.showMenu, shownResults = _getInitialState3.shownResults; this.setState({ activeIndex: activeIndex, activeItem: activeItem, showMenu: showMenu, shownResults: shownResults }); } }); export default onClickOutside(Refers);