import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Select, Balloon, Icon } from '@alife/next'; import * as acorn from 'acorn'; import { isJSExpression, generateI18n } from './locale/utils'; import zhCN from './locale/zh-CN'; import './index.scss'; const { Option, AutoComplete } = Select; const { Tooltip } = Balloon; const helpMap = { this: '容器上下文对象', 'state': '容器的state', 'props': '容器的props', 'context': '容器的context', 'schema': '页面上下文对象', 'component': '组件上下文对象', 'constants': '应用常量对象', 'utils': '应用工具对象', 'dataSourceMap': '容器数据源Map', 'field': '表单Field对象' } export default class ExpressionView extends PureComponent { static displayName = 'Expression'; static propTypes = { context: PropTypes.object, dataSource: PropTypes.array, locale: PropTypes.string, messages: PropTypes.object, onChange: PropTypes.func, placeholder: PropTypes.string, value: PropTypes.string }; static defaultProps = { context: {}, dataSource: [], locale: 'zh-CN', messages: zhCN, onChange: () => {}, placeholder: '', value: '' }; expression: React.RefObject; i18n: any; t: void; $input: any; listenerFun: ((event: any) => void) | undefined; static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) { if (isJSExpression(val)) { if (typeof val === 'object') { return val.value; } else if (typeof val === 'string') { let arr = val.match(/^\{\{(.*?)\}\}$/); if (arr) return arr[1]; } } return val; } constructor(props: Readonly<{}>) { super(props); this.expression = React.createRef(); this.i18n = generateI18n(props.locale, props.messages); this.state = { value: ExpressionView.getInitValue(props.value), context: props.context || {}, dataSource: props.dataSource || [] }; } static getDerivedStateFromProps(props: { value: any; }, state: { preValue: any; }) { let curValue = ExpressionView.getInitValue(props.value); if (curValue !== state.preValue) { return { preValue: curValue, value: curValue }; } return null; } onChange(value: string, actionType: string) { let realInputValue = value; let realDataSource = null; let nextCursorIndex: number; //更新值 if (actionType === 'itemClick' || actionType === 'enter') { let curValue = this.state.value; if (curValue) { realInputValue = curValue + realInputValue; } } //更新数据源 let newState = { value: realInputValue }; if (realDataSource !== null) newState.dataSource = realDataSource; this.setState(newState, () => { nextCursorIndex && this.setInputCursorPosition(nextCursorIndex); }); //默认加上变量表达式 this.t && clearTimeout(this.t); this.t = setTimeout(() => { const { onChange } = this.props; // realInputValue = realInputValue ? `{{${realInputValue}}}` : undefined; onChange && onChange({ type: 'JSExpression', value: realInputValue }); }, 300); } /** * 获取AutoComplete数据源 * @param {String} * @return {Array} */ getDataSource(tempStr: string): Array { if (/[^\w\.]$/.test(tempStr)) { return []; } else if (tempStr === null || tempStr === '') { return this.getContextKeys([]); } else if (/\w\.$/.test(tempStr)) { let currentField = this.getCurrentFiled(tempStr); if (!currentField) return null; let tempKeys = this.getObjectKeys(currentField.str); tempKeys = this.getContextKeys(tempKeys); if (!tempKeys) return null; return tempKeys; } else if (/\.$/.test(tempStr)) { return []; } else { return null; } } /** * 获取光标前的对象字符串,语法解析获取对象字符串 * @param {String} str 模板字符串 * @return {String} 光标前的对象字符串 */ getCurrentFiled(str: string | any[]) { str += 'x'; //.后面加一个x字符,便于acorn解析 try { let astTree = acorn.parse(str); let right = astTree.body[0].expression.right || astTree.body[0].expression; if (right.type === 'MemberExpression') { let { start, end } = right; str = str.slice(start, end); return { str, start, end }; } } catch (e) { return null; } } /** * 获取输入的上下文信息 * @param {Array} * @return {Array} */ getContextKeys(keys: []) { const editor = this.props.field.editor; console.log(editor); const limitKeys = ['schema', 'utils', 'constants']; if (keys.length === 0) return limitKeys; if (!limitKeys.includes(keys[0])) return []; let result = []; let keyValue = editor; let assert = false; keys.forEach(item => { if (!keyValue[item] || typeof keyValue[item] !== 'object') { assert = true; } if (keyValue[item]) { keyValue = keyValue[item]; } }) if (assert) return []; result = Object.keys(keyValue); return result; // return utilsKeys.concat(constantsKeys).concat(schemaKeys); } /*过滤key */ filterKey(obj: any, name: string) { let filterKeys = [ 'reloadDataSource', 'REACT_HOT_LOADER_RENDERED_GENERATION', 'refs', 'updater', 'appHelper', 'isReactComponent', 'forceUpdate', 'setState', 'isPureReactComponent' ]; let result = []; for (let key in obj) { if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) { result.push(`${name}.${key}`); } } return result; } /** * 根据输入项进行筛选 * @param {String} * @param {String} * @return {Boolen} */ filterOption(inputValue: string, item: { value: string | any[]; }) { const cursorIndex = this.getInputCursorPosition(); let preStr = inputValue.substr(0, cursorIndex); let lastKey: string[] = preStr.split('.').slice(-1); if (!lastKey) return true; if (item.value.indexOf(lastKey) > -1) return true; return false; } // handleClick = () => { // this.props.field.editor.emit('variableBindDialog.open'); // } render() { const { value, dataSource } = this.state; const { placeholder } = this.props; const isValObject = !!(value == '[object Object]'); let title = isValObject ? this.i18n('valueIllegal') : (value || placeholder || this.i18n('jsExpression')).toString(); const cursorIndex = this.getInputCursorPosition(); let childNode = cursorIndex ? (
{title.substr(0, cursorIndex)} | {title.substr(cursorIndex)}
) : ( title ); return (
{'{{'}} innerAfter={{'}}'}} popupClassName="expression-setter-item-inner" itemRender={({ value }) => { console.log(value); return ( ); }} onChange={this.onChange.bind(this)} filter={this.filterOption.bind(this)} />
) } > {childNode} ); } componentDidMount() { this.$input = this.findInputElement(); if (this.$input) { this.listenerFun = event => { let isMoveKey = !!(event.type == 'keyup' && ~[37, 38, 39, 91].indexOf(event.keyCode)); let isMouseup = event.type == 'mouseup'; if (isMoveKey || isMouseup) { let dataSource = this.getDataSource(this.state.value) || []; this.setState({ dataSource }); } }; this.$input.addEventListener('keyup', this.listenerFun, false); this.$input.addEventListener('mouseup', this.listenerFun, false); } } componentWillUnmount() { if (this.listenerFun && this.$input) { this.$input.removeEventListener('keyup', this.listenerFun, false); this.$input.removeEventListener('mouseup', this.listenerFun, false); } } /** * 获取Input输入框DOM节点 */ findInputElement() { return this.expression.current.children[0].getElementsByTagName('input')[0]; } /** * 获取光标位置 * */ getInputCursorPosition() { if (!this.$input) return; return this.$input.selectionStart; } /* * 字符串取得对象keys */ getObjectKeys(str: string) { let keys: string | any[] = []; if (str) keys = str.split('.'); return keys.slice(0, keys.length - 1); } /* * 设置input组件光标位置在闭合}前 */ setInputCursorPosition(idx: number) { this.$input.setSelectionRange(idx, idx); this.forceUpdate(); } }