q-antd
Version:
双绑形式的antd。受控组件的双绑、Form表单验证
349 lines (327 loc) • 11.5 kB
JavaScript
/*
* @Author: shiyong.yin
* @Date: 2018-01-16 12:00:13
* @Last Modified by: shiyong.yin
* @Description: 封装Provider为Form,使其具有校验功能,需配合Collect使用
* @Usage:
*/
/**
* Form fillCount=1 labelWidth=20 layout=[flex, block,block]
* Flexbox
* Flex|Block
* ItemWrap
* Item
* ...
* ...
* Block|Flex
* Action
*/
import React from 'react';
import * as mobx from 'mobx';
const { observable } = mobx;
import { Provider, observer } from 'mobx-react';
import { autobind } from 'core-decorators';
import Flexbox from '../flexbox';
const { Flex, Block } = Flexbox;
import PropTypes from 'prop-types';
import setProps from 'set-props';
import FormItem from './item';
import Action from './action';
import childrenUniKey from '../_util/childrenUniKey';
import _ from 'lodash';
export default class Form extends React.Component {
fields = []; //如果有prop属性,才push到fields数组中
duplexer = []; //只要有duplex属性,就push到此数组中
initialModel = null; //缓存下model的初始值,用于resetFields
// @observable rules = {}; //存储校验规则
validate(fieldName = '', fn = () => 1) {
return new Promise((resolve, reject) => {
//单独校验某一项
if (fieldName && typeof fieldName !== 'function') {
let valid = true;
this.fields.map(field =>{
if(fieldName === field.props.prop) {
field.validate('', errors => {
if (errors) {
valid = false;
}
resolve(valid);
fn(valid);
});
}
})
} else { //校验全部
let valid = true;
let count = 0;
if (this.fields.length > 0) {
this.fields.map(field => {
field.validate('', errors => {
if (errors) {
valid = false;
}
if (++count === this.fields.length) {
resolve(valid);
fn(valid);
}
});
});
} else {
resolve(valid);
fn(valid);
}
}
});
}
resetFieldsStyle() {
this.fields.map(field => {
field.resetField();
});
}
resetFields() {
Object.assign(this.props.model, this.initialModel);
}
// componentWillReceiveProps(nextProps){
// // fix mobx warning
// // MobX Provider: Provided store 'rules' has changed. Please avoid replacing stores as the change might not propagate to all children
// if(this.rules !== nextProps.rules){
// this.rules = nextProps.rules;
// }
// }
componentDidMount() {
// if (this.rules !== this.props.rules) {
// this.rules = this.props.rules;
// }
this.initialModel = _.merge({}, mobx.toJS(this.props.model));
}
render() {
/**
* actionPosition定位Action的位置
* right-flex-start
* right-flex-end
* right-center
* bottom-flex-start
* bottom-flex-end
* bottom-center
*/
const {
children,
layout,
fillCount,
inline,
labelWidth,
labelPosition,
manualLayout,
actionPosition,
needCls,
labelStyle,
style,
...remain
} = this.props;
const actionPositionArr = actionPosition.split(':');
const mainActionPosition = actionPositionArr[0] || 'right';
const justifyActionPosition = actionPositionArr[1] || 'flex-start';
const alignActionPosition = actionPositionArr[2] || 'flex-end';
/**
* manual:
* false - 使用Form内部提供的布局,能满足绝大多数需求
* true - 不使用Form内部提供的布局,需要自己布局
*/
let formItems = [],
actions = [],
formItem = [];
let formItemTotal = 0,
formItemCount = 0,
formTotalCount = 0;
if (!manualLayout) {
React.Children.map(children, (item, i) => {
if (item && item.type && item.type.name) {
if (
item.type.name === 'FormItem' ||
(item.type.wrappedComponent &&
item.type.wrappedComponent.name === 'FormItem')
) {
formItemTotal++;
}
}
});
//对children内容进行重新布局,这就引起了unique key prop的warning,可使用childrenUniKey解决
React.Children.map(children, (item, i) => {
if (item && item.type && item.type.name) {
if (
item.type.name === 'Action' ||
(item.type.wrappedComponent &&
item.type.wrappedComponent.name === 'Action')
) {
actions.push(item);
} else if (
item.type.name === 'FormItem' ||
(item.type.wrappedComponent &&
item.type.wrappedComponent.name === 'FormItem')
) {
formItemCount++;
formTotalCount++;
formItem.push(item);
//将formItem进行分组,不够一组用空的FormItem进行补齐
if (fillCount === formItemCount) {
formItems.push(_.merge([], formItem));
formItemCount = 0;
formItem = [];
} else if (formTotalCount === formItemTotal) {
let nurse = fillCount - formItemCount;
while (nurse-- > 0) {
formItem.push(<FormItem />);
}
formItems.push(formItem);
}
} else {
console.error(
`Collect中出现了非formItems,Action,FormItem的控件:${
item.type.name
}`
);
}
}
});
}
const layoutArr = layout.split(':');
const SearchComponentLayout =
(layoutArr[0] && (layoutArr[0] === 'flex' || layoutArr[0] === '')) ||
layoutArr[0] === void 0
? Flex
: Block;
const ActionComponentLayout =
(layoutArr[1] && (layoutArr[1] === 'block' || layoutArr[1] === '')) ||
layoutArr[1] === void 0
? Block
: Flex;
const ItemComponentLayout =
(layoutArr[2] && (layoutArr[2] === 'block' || layoutArr[2] === '')) ||
layoutArr[2] === void 0
? Block
: Flex;
// inline && (SearchComponentLayout = Flex);
const formCls = needCls ? 'i-form' : '';
const addons = {};
if (!remain.rules) {
//如果用户没有传入rules属性,需要确保此属性出现在Provider上
addons.rules = {};
}
return (
<Provider
fields={this.fields}
duplexer={this.duplexer}
rules={this.props.rules}
ItemComponentLayout={ItemComponentLayout}
getDuplexFromElement={() => 1}
type="block"
labelWidth={labelWidth}
labelStyle={labelStyle}
{...remain}
{...addons}
labelPosition={labelPosition}>
{manualLayout ? (
<div className={formCls} style={style}>
{children}
</div>
) : inline ? (
<Flexbox className={formCls} style={style}>
{/* {childrenUniKey(formItems)} */}
{/* {React.Children.map(formItems, (formItem, i) => {
return React.cloneElement(formItem, { key: i });
})} */}
{childrenUniKey(formItems)}
</Flexbox>
) : (
<div>
<Flexbox
className={formCls}
style={style}
alignItems={
mainActionPosition === 'right'
? alignActionPosition
: 'flex-start'
}>
<SearchComponentLayout>
{formItems.map((formItemWrap, i) => {
//flex的时候,需要确保i-form-item-wrap元素是个Blcok,这个FormItem中flex才能生效
return layout[2] === 'flex' ? (
<Block
key={`form-item-wrap-${i}`}
className="i-form-item-wrap">
{childrenUniKey(formItemWrap)}
{/* {React.Children.map(formItemWrap, (formItems, i) => {
return React.cloneElement(React.Children.map(formItems, (formItem, j) => {
return React.cloneElement(formItem, { key: j })
}), { key: i });
})} */}
{/* {React.Children.map(formItemWrap, (formItems, i) => {
let formItems = React.cloneElement(formItems, { key: i });
if (React.Children.count(formItems.props.children) > 1) {
React.Children.map(formItems.props.children, (cc, j) => {
cc.key = j;
})
}
return formItems;
})} */}
</Block>
) : (
<Flexbox
key={`form-item-wrap-${i}`}
className="i-form-item-wrap">
{childrenUniKey(formItemWrap)}
{/* {React.Children.map(formItemWrap, (formItems, i) => {
return React.cloneElement(React.Children.map(formItems, (formItem, j) => {
return React.cloneElement(formItem, { key: j })
}), { key: i });
})} */}
{/* {React.Children.map(formItemWrap, (formItems, i) => {
return React.cloneElement(formItems, { key: i });
})} */}
</Flexbox>
);
})}
</SearchComponentLayout>
{mainActionPosition === 'right' ? (
<ActionComponentLayout
style={{
display: 'flex',
alignItems: alignActionPosition,
justifyContent: justifyActionPosition,
}}>
{actions}
</ActionComponentLayout>
) : null}
</Flexbox>
{mainActionPosition === 'bottom' ? (
<Flexbox
justifyContent={justifyActionPosition}
alignItems={alignActionPosition}
className="i-form-action">
{actions}
</Flexbox>
) : null}
</div>
)}
</Provider>
);
}
}
Form.FormItem = FormItem;
Form.Action = Action;
setProps(Form, {
validateOnChange: [e => 1, PropTypes.func],
validateOnBlur: [e => 1, PropTypes.func],
inline: [false, PropTypes.bool],
layout: ['flex:block:block', PropTypes.string],
actionPosition: ['right:flex-start:flex-end', PropTypes.string],
labelPosition: ['right:center', PropTypes.string],
labelStyle: [null, PropTypes.object],
fillCount: [1, PropTypes.number],
manualLayout: [false, PropTypes.bool],
labelWidth: [0, PropTypes.number], //labelWidth为0时,会将0替换为‘auto’
needCls: [true, PropTypes.bool],
});