zent
Version:
一套前端设计语言和基于React的实现
274 lines (234 loc) • 6.41 kB
JavaScript
import React, { PureComponent, Component } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import assign from 'lodash/assign';
const NOT_EVENT_MSG =
'onInputChange expects an `Event` with { target: { name, value } } as argument';
export class DesignEditor extends (PureComponent || Component) {
static propTypes = {
value: PropTypes.object,
onChange: PropTypes.func.isRequired,
// 验证状态
validation: PropTypes.object.isRequired,
// 是否强制显示所有错误
showError: PropTypes.bool.isRequired,
// 用来和 Design 交互
design: PropTypes.object.isRequired,
// 自定义配置
globalConfig: PropTypes.object
};
// 以下属性需要子类重写
// 组件的类型
static designType = 'unknown';
// 组件的描述
static designDescription = '未知组件';
// value 的验证函数
// eslint-disable-next-line
static validate(value, prevValue, changedProps) {
return new Promise(resolve => resolve({}));
}
// 添加组件实例时的初始值
static getInitialValue = () => {};
constructor(props) {
super(props);
this.state = assign({}, this.state, {
meta: {}
});
this.validateValue();
}
/**
* 通用的 Input 元素 onChange 回调函数
*
* 适用于 Input, Checkbox, Select, Radio
*/
onInputChange = evt => {
// 如果抛出来的不是 Event 对象,直接丢给 onChange
if (!isEventLikeObject(evt)) {
throw new Error(NOT_EVENT_MSG);
}
const { onChange } = this.props;
const { target } = evt;
const { name, type } = target;
let { value } = target;
if (type === 'checkbox') {
value = target.checked;
}
onChange({
[name]: value
});
this.setMetaProperty(name, 'dirty');
};
/**
* 有些组件的 onChange 事件抛出来的不是 Event 对象
*
* 适用于 Slider, Switch, DatePicker 以及其它自定义组件
*/
onCustomInputChange = name => value => {
const { onChange } = this.props;
onChange({ [name]: value });
this.setMetaProperty(name, 'dirty');
};
/**
* 处理 Input 元素的 blur 事件。
*/
onInputBlur = evt => {
// 如果抛出来的不是 Event 对象,直接丢给 onChange
if (!isEventLikeObject(evt)) {
throw new Error(NOT_EVENT_MSG);
}
const { target: { name } } = evt;
this.onCustomInputBlur(name);
};
/**
* 有些组件没有 onBlur 事件,用这个函数处理
*/
onCustomInputBlur = name => {
this.setMetaProperty(name, 'touched');
this.validateValue();
};
/**
* 获取 Field 的 meta 属性,包括 dirty, touched 等
* @param {string} name Field 名字
* @param {string} property meta 属性名字
*/
getMetaProperty(name, property) {
const { meta } = this.state;
return !!(meta && meta[name] && meta[name][property]);
}
/**
* 设置 Field 的 meta 属性
* @param {string} name Field 名字
* @param {string} property meta 属性名字
* @param {any} state 属性的值
*/
setMetaProperty(name, property, state = true) {
const { meta } = this.state;
const states = meta[name];
if (!states || states[property] !== state) {
this.setState({
meta: assign({}, meta, {
[name]: assign({}, states, { [property]: state })
})
});
}
}
/**
* 返回表单是否有错误
*/
isValid() {
const { validation } = this.props;
return Object.keys(validation).length > 0;
}
isInvalid() {
return !this.isValid();
}
/**
* 触发一次表单校验
*/
validateValue() {
const { value, design } = this.props;
design.validateComponentValue(value, value, []).then(errors => {
const id = design.getUUID(value);
design.setValidation({ [id]: errors });
});
}
}
/**
* 表单每个域的基础样式
*/
export class ControlGroup extends (PureComponent || Component) {
static propTypes = {
showError: PropTypes.bool,
error: PropTypes.node,
showLabel: PropTypes.bool,
helpDesc: PropTypes.string,
label: PropTypes.node,
// 自定义label对齐方式
labelAlign: PropTypes.string,
// 点击 label 区域时是否 focus 到 control 的 input 上
focusOnLabelClick: PropTypes.bool,
required: PropTypes.bool,
className: PropTypes.string,
prefix: PropTypes.string
};
static defaultProps = {
required: false,
showError: false,
showLabel: true,
focusOnLabelClick: true,
error: '',
prefix: 'zent'
};
render() {
const {
className,
prefix,
showError,
error,
showLabel,
label,
labelAlign,
helpDesc,
required,
children,
focusOnLabelClick
} = this.props;
const errorVisible = showError && error;
return (
<div
className={cx(`${prefix}-design-editor__control-group`, className, {
'has-error': errorVisible
})}
>
{React.createElement(
focusOnLabelClick ? 'label' : 'div',
{
className: `${prefix}-design-editor__control-group-container`
},
showLabel ? (
<div
className={cx(
`${prefix}-design-editor__control-group-label`,
labelAlign &&
`${prefix}-design-editor__control-group-label--${labelAlign}`
)}
>
{required && (
<span
className={`${prefix}-design-editor__control-group-required-star`}
>
*
</span>
)}
{label}
</div>
) : null,
<div className={`${prefix}-design-editor__control-group-control`}>
{children}
{helpDesc && (
<div
className={`${prefix}-design-editor__control-group-help-desc`}
>
{helpDesc}
</div>
)}
</div>
)}
{errorVisible && (
<div className={`${prefix}-design-editor__control-group-error`}>
{error}
</div>
)}
</div>
);
}
}
function isEventLikeObject(evt) {
return (
evt &&
evt.target &&
evt.target.name &&
evt.preventDefault &&
evt.stopPropagation
);
}