import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Select, Balloon } from '@alife/next'; import * as acorn from 'acorn'; import { isJSExpression, generateI18n } from './locale/utils'; import zhCN from './locale/zh-CN'; const { Option, AutoComplete } = Select; const { Tooltip } = Balloon; const helpMap = { this: '容器上下文对象', 'this.state': '容器的state', 'this.props': '容器的props', 'this.context': '容器的context', 'this.page': '页面上下文对象', 'this.component': '组件上下文对象', 'this.constants': '应用常量对象', 'this.utils': '应用工具对象', 'this.dataSourceMap': '容器数据源Map', 'this.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; 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; const cursorIndex = this.getInputCursorPosition(); let nextCursorIndex: number; //更新值 if (actionType === 'itemClick' || actionType === 'enter') { let curValue = this.state.value; if (curValue) { //如果是非.结束,则替换当前这个变量; let preStr = curValue.substr(0, cursorIndex); let nextStr = curValue.substr(cursorIndex); let preArr = preStr.split('.'); let preArrLen = preArr.length; let tarPreStr = ''; if (!preArr[preArrLen - 1]) { //如果是.结束,则增加到.后面 if (preArr[preArrLen - 2] === 'this') { preArr = preArr.slice(0, preArrLen - 2); preArr.push(value); tarPreStr = preArr.join('.'); } else { tarPreStr = preStr + value; } } else { if (preArr[preArrLen - 2] === 'this') { preArr = preArr.slice(0, preArrLen - 2); } else { preArr = preArr.slice(0, preArrLen - 1); } preArr.push(value); tarPreStr = preArr.join('.'); } realInputValue = tarPreStr + nextStr; realDataSource = this.getDataSource(tarPreStr + '.') || []; nextCursorIndex = tarPreStr.length; } } else { let tarPreStr = value.substr(0, cursorIndex); if (tarPreStr) { let lastChar = tarPreStr.charAt(tarPreStr.length - 1); if (lastChar === '.') { realDataSource = this.getDataSource(tarPreStr) || []; } else { realDataSource = this.getDataSource(tarPreStr + '.'); } } else { realDataSource = this.getDataSource('this.'); } } //更新数据源 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 (tempStr === '' || /[^\w\.]$/.test(tempStr)) { return this.getDataSource('this.') || []; } 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; //给默认情况增加this if (tempStr === 'this.') { tempKeys = tempKeys.map((item: string) => { return 'this.' + item; }); tempKeys.unshift('this'); } 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: any) { // let context = {}; // const { appHelper } = this.context; // const activeKey = appHelper && appHelper.activeKey; // if (!activeKey) return; // const activeCtx = appHelper.schemaHelper.compCtxMap && appHelper.schemaHelper.compCtxMap[activeKey]; // if (!activeCtx) return null; // let __self = activeCtx; // if (keys && keys.length > 1) { // keys.shift(0); // let path = '/' + keys.join('/'); // path = path.replace(/[\[\]]/g, '/'); // context = jsonuri.get(__self, path); // if (context && typeof context === 'object') { // return this.filterKey(context); // } // } else if (keys && keys[0] === 'this') { // return this.filterKey(__self); // } // return null; return ['page', 'component']; } /*过滤key */ filterKey(obj: any) { 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(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 = preStr.split('.').slice(-1); if (!lastKey) return true; if (item.value.indexOf(lastKey) > -1) return true; return false; } 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={{'}}'}} itemRender={({ 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 = []; if (str) keys = str.split('.'); return keys.slice(0, keys.length - 1); } /* * 设置input组件光标位置在闭合}前 */ setInputCursorPosition(idx: number) { this.$input.setSelectionRange(idx, idx); this.forceUpdate(); } }