Form表单
具有数据收集、校验和提交功能的表单,包含复选框、单选框、输入框、下拉选择框等元素。
表单
我们为 form
提供了以下三种排列方式:
水平排列:标签和表单控件水平排列;(默认)
垂直排列:标签和表单控件上下垂直排列;
行内排列:表单项水平行内排列。
表单域
表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等。
这里我们封装了表单域 <Form.Item />
。
<Form.Item {...props}>
{children}
</Form.Item>
代码演示
水平登录栏,常用在顶部导航栏中。
import { Form, Icon, Input, Button } from 'choerodon-ui';
const FormItem = Form.Item;
function hasErrors(fieldsError) {
return Object.keys(fieldsError).some(field => fieldsError[field]);
}
class HorizontalLoginForm extends React.Component {
componentDidMount() {
// To disabled submit button at the beginning.
this.props.form.validateFields();
}
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
// Only show error after a field is touched.
const userNameError = isFieldTouched('userName') && getFieldError('userName');
const passwordError = isFieldTouched('password') && getFieldError('password');
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem validateStatus={userNameError ? 'error' : ''} help={userNameError || ''}>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="person_outline" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</FormItem>
<FormItem validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock_outline" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
Log in
</Button>
</FormItem>
</Form>
);
}
}
const WrappedHorizontalLoginForm = Form.create()(HorizontalLoginForm);
ReactDOM.render(<WrappedHorizontalLoginForm />, mountNode);
普通的登录框,可以容纳更多的元素。
import { Form, Icon, Input, Button, Checkbox } from 'choerodon-ui';
const FormItem = Form.Item;
class NormalLoginForm extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit} className="login-form">
<FormItem>
{getFieldDecorator('userName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input
prefix={<Icon type="person_outline" style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder="Username"
/>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('password', {
rules: [{ required: true, message: 'Please input your Password!' }],
})(
<Input
prefix={<Icon type="lock_outline" style={{ color: 'rgba(0,0,0,.25)' }} />}
type="password"
placeholder="Password"
/>,
)}
</FormItem>
<FormItem>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>Remember me</Checkbox>)}
<a className="login-form-forgot" href="">
Forgot password
</a>
<Button type="primary" funcType="raised" htmlType="submit" className="login-form-button">
Log in
</Button>
Or <a href="">register now!</a>
</FormItem>
</Form>
);
}
}
const WrappedNormalLoginForm = Form.create()(NormalLoginForm);
ReactDOM.render(<WrappedNormalLoginForm />, mountNode);
#components-form-demo-normal-login .login-form {
max-width: 300px;
}
#components-form-demo-normal-login .login-form-forgot {
float: right;
}
#components-form-demo-normal-login .login-form-button {
width: 100%;
}
用户填写必须的信息以注册新用户。
import {
Form,
Input,
Tooltip,
Icon,
Cascader,
Row,
Col,
Checkbox,
Button,
AutoComplete,
InputNumber,
} from 'choerodon-ui';
const FormItem = Form.Item;
const AutoCompleteOption = AutoComplete.Option;
const residences = [
{
value: 'zhejiang',
label: 'Zhejiang',
children: [
{
value: 'hangzhou',
label: 'Hangzhou',
children: [
{
value: 'xihu',
label: 'West Lake',
},
],
},
],
},
{
value: 'jiangsu',
label: 'Jiangsu',
children: [
{
value: 'nanjing',
label: 'Nanjing',
children: [
{
value: 'zhonghuamen',
label: 'Zhong Hua Men',
},
],
},
],
},
];
class RegistrationForm extends React.Component {
state = {
confirmDirty: false,
showLengthInfo: true,
autoCompleteResult: [],
};
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (err.nickname) {
this.setState({
showLengthInfo: false,
});
}
if (!err) {
console.log('Received values of form: ', values);
}
});
};
handleConfirmBlur = e => {
const value = e.target.value;
this.setState({ confirmDirty: this.state.confirmDirty || !!value });
};
compareToFirstPassword = (rule, value, callback) => {
const form = this.props.form;
if (value && value !== form.getFieldValue('password')) {
callback('Two passwords that you enter is inconsistent!');
} else {
callback();
}
};
validateToNextPassword = (rule, value, callback) => {
const form = this.props.form;
if (value && this.state.confirmDirty) {
form.validateFields(['confirm'], { force: true });
}
callback();
};
handleWebsiteChange = value => {
let autoCompleteResult;
if (!value) {
autoCompleteResult = [];
} else {
autoCompleteResult = ['.com', '.org', '.net'].map(domain => `${value}${domain}`);
}
this.setState({ autoCompleteResult });
};
handleNickName = () => {
const { showLengthInfo } = this.state;
if (!showLengthInfo) {
this.setState({
showLengthInfo: true,
});
}
};
render() {
const { getFieldDecorator } = this.props.form;
const { autoCompleteResult, showLengthInfo } = this.state;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 16,
offset: 8,
},
},
};
const websiteOptions = autoCompleteResult.map(website => (
<AutoCompleteOption key={website}>{website}</AutoCompleteOption>
));
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="E-mail">
{getFieldDecorator('email', {
rules: [
{
type: 'email',
message: 'The input is not valid E-mail!',
},
{
required: true,
message: 'Please input your E-mail!',
},
],
})(<Input label="email" />)}
</FormItem>
<FormItem {...formItemLayout} label="Password">
{getFieldDecorator('password', {
rules: [
{
required: true,
message: 'Please input your password!',
},
{
validator: this.validateToNextPassword,
},
],
})(<Input showPasswordEye label="password" type="password" />)}
</FormItem>
<FormItem {...formItemLayout} label="Confirm Password">
{getFieldDecorator('confirm', {
rules: [
{
required: true,
message: 'Please confirm your password!',
},
{
validator: this.compareToFirstPassword,
},
],
})(
<Input
showPasswordEye
label="password"
type="password"
onBlur={this.handleConfirmBlur}
/>,
)}
</FormItem>
<FormItem
{...formItemLayout}
label={
<span>
Nickname
<Tooltip title="What do you want others to call you?">
<Icon type="question-circle-o" />
</Tooltip>
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(
<Input
maxLength={20}
label="名称"
showLengthInfo={showLengthInfo}
onChange={this.handleNickName}
/>,
)}
</FormItem>
<FormItem {...formItemLayout}>
{getFieldDecorator('age', {
rules: [
{
required: true,
message: 'Please input your age!',
type: 'number',
whitespace: true,
},
],
initialValue: 20,
})(<InputNumber label="Age" min={1} max={100} />)}
</FormItem>
<FormItem {...formItemLayout} label="Habitual Residence">
{getFieldDecorator('residence', {
initialValue: ['zhejiang', 'hangzhou', 'xihu'],
rules: [
{ type: 'array', required: true, message: 'Please select your habitual residence!' },
],
})(<Cascader options={residences} />)}
</FormItem>
<FormItem {...formItemLayout}>
{getFieldDecorator('website', {
rules: [{ required: true, message: 'Please input website!' }],
})(
<AutoComplete
dataSource={websiteOptions}
onChange={this.handleWebsiteChange}
placeholder="website"
label="Website"
/>,
)}
</FormItem>
<FormItem {...formItemLayout} extra="We must make sure that your are a human.">
<Row gutter={8}>
<Col span={12}>
{getFieldDecorator('captcha', {
rules: [{ required: true, message: 'Please input the captcha you got!' }],
})(<Input label="Captcha" />)}
</Col>
<Col span={12}>
<Button funcType="raised">Get captcha</Button>
</Col>
</Row>
</FormItem>
<FormItem {...tailFormItemLayout}>
{getFieldDecorator('agreement', {
valuePropName: 'checked',
})(
<Checkbox>
I have read the <a href="">agreement</a>
</Checkbox>,
)}
</FormItem>
<FormItem {...tailFormItemLayout}>
<Button type="primary" funcType="raised" htmlType="submit">
Register
</Button>
</FormItem>
</Form>
);
}
}
const WrappedRegistrationForm = Form.create()(RegistrationForm);
ReactDOM.render(<WrappedRegistrationForm />, mountNode);
三列栅格式的表单排列方式,常用于数据表格的高级搜索。
有部分定制的样式代码,由于输入标签长度不确定,需要根据具体情况自行调整。
import { Form, Row, Col, Input, Button, Icon } from 'choerodon-ui';
const FormItem = Form.Item;
class AdvancedSearchForm extends React.Component {
state = {
expand: false,
};
handleSearch = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
console.log('Received values of form: ', values);
});
};
handleReset = () => {
this.props.form.resetFields();
};
toggle = () => {
const { expand } = this.state;
this.setState({ expand: !expand });
};
// To generate mock Form.Item
getFields() {
const count = this.state.expand ? 10 : 6;
const { getFieldDecorator } = this.props.form;
const children = [];
for (let i = 0; i < 10; i++) {
children.push(
<Col span={8} key={i} style={{ display: i < count ? 'block' : 'none' }}>
<FormItem label={`Field ${i}`}>
{getFieldDecorator(`field-${i}`)(<Input placeholder="placeholder" />)}
</FormItem>
</Col>,
);
}
return children;
}
render() {
return (
<Form className="c7n-advanced-search-form" onSubmit={this.handleSearch}>
<Row gutter={24}>{this.getFields()}</Row>
<Row>
<Col span={24} style={{ textAlign: 'right' }}>
<Button type="primary" htmlType="submit">
Search
</Button>
<Button style={{ marginLeft: 8 }} onClick={this.handleReset}>
Clear
</Button>
<a style={{ marginLeft: 8, fontSize: 12 }} onClick={this.toggle}>
Collapse <Icon type={this.state.expand ? 'up' : 'down'} />
</a>
</Col>
</Row>
</Form>
);
}
}
const WrappedAdvancedSearchForm = Form.create()(AdvancedSearchForm);
ReactDOM.render(
<div>
<WrappedAdvancedSearchForm />
<div className="search-result-list">Search Result List</div>
</div>,
mountNode,
);
.c7n-advanced-search-form {
padding: 24px;
background: #fbfbfb;
border: 1px solid #d9d9d9;
border-radius: 6px;
}
.c7n-advanced-search-form .c7n-form-item {
display: flex;
}
.c7n-advanced-search-form .c7n-form-item-control-wrapper {
flex: 1;
}
当用户访问一个展示了某个列表的页面,想新建一项但又不想跳转页面时,可以用 Modal 弹出一个表单,用户填写必要信息后创建新的项。
import { Button, Modal, Form, Input, Radio } from 'choerodon-ui';
const FormItem = Form.Item;
const CollectionCreateForm = Form.create()(
// eslint-disable-next-line
class extends React.Component {
render() {
const { visible, onCancel, onCreate, form } = this.props;
const { getFieldDecorator } = form;
return (
<Modal
visible={visible}
title="Create a new collection"
okText="Create"
onCancel={onCancel}
onOk={onCreate}
>
<Form layout="vertical">
<FormItem label="Title">
{getFieldDecorator('title', {
rules: [{ required: true, message: 'Please input the title of collection!' }],
})(<Input />)}
</FormItem>
<FormItem label="Description">
{getFieldDecorator('description')(<Input type="textarea" />)}
</FormItem>
<FormItem className="collection-create-form_last-form-item">
{getFieldDecorator('modifier', {
initialValue: 'public',
})(
<Radio.Group>
<Radio value="public">Public</Radio>
<Radio value="private">Private</Radio>
</Radio.Group>,
)}
</FormItem>
</Form>
</Modal>
);
}
},
);
class CollectionsPage extends React.Component {
state = {
visible: false,
};
showModal = () => {
this.setState({ visible: true });
};
handleCancel = () => {
this.setState({ visible: false });
};
handleCreate = () => {
const form = this.formRef.props.form;
form.validateFields((err, values) => {
if (err) {
return;
}
console.log('Received values of form: ', values);
form.resetFields();
this.setState({ visible: false });
});
};
saveFormRef = formRef => {
this.formRef = formRef;
};
render() {
return (
<div>
<Button type="primary" onClick={this.showModal}>
New Collection
</Button>
<CollectionCreateForm
wrappedComponentRef={this.saveFormRef}
visible={this.state.visible}
onCancel={this.handleCancel}
onCreate={this.handleCreate}
/>
</div>
);
}
}
ReactDOM.render(<CollectionsPage />, mountNode);
.collection-create-form_last-form-item {
margin-bottom: 0;
}
动态增加、减少表单项。
import { Form, Input, Icon, Button } from 'choerodon-ui';
const FormItem = Form.Item;
let uuid = 0;
class DynamicFieldSet extends React.Component {
remove = k => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue('keys');
// We need at least one passenger
if (keys.length === 1) {
return;
}
// can use data-binding to set
form.setFieldsValue({
keys: keys.filter(key => key !== k),
});
};
add = () => {
const { form } = this.props;
// can use data-binding to get
const keys = form.getFieldValue('keys');
const nextKeys = keys.concat(uuid);
uuid++;
// can use data-binding to set
// important! notify form to detect changes
form.setFieldsValue({
keys: nextKeys,
});
};
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator, getFieldValue } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 4 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 20 },
},
};
const formItemLayoutWithOutLabel = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 20, offset: 4 },
},
};
getFieldDecorator('keys', { initialValue: [] });
const keys = getFieldValue('keys');
const formItems = keys.map((k, index) => {
return (
<FormItem
{...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
label={index === 0 ? 'Passengers' : ''}
required={false}
key={k}
>
{getFieldDecorator(`names[${k}]`, {
validateTrigger: ['onChange', 'onBlur'],
rules: [
{
required: true,
whitespace: true,
message: "Please input passenger's name or delete this field.",
},
],
})(<Input placeholder="passenger name" style={{ width: '60%', marginRight: 8 }} />)}
{keys.length > 1 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
disabled={keys.length === 1}
onClick={() => this.remove(k)}
/>
) : null}
</FormItem>
);
});
return (
<Form onSubmit={this.handleSubmit}>
{formItems}
<FormItem {...formItemLayoutWithOutLabel}>
<Button type="dashed" onClick={this.add} style={{ width: '60%' }}>
<Icon type="plus" /> Add field
</Button>
</FormItem>
<FormItem {...formItemLayoutWithOutLabel}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
const WrappedDynamicFieldSet = Form.create()(DynamicFieldSet);
ReactDOM.render(<WrappedDynamicFieldSet />, mountNode);
.dynamic-delete-button {
cursor: pointer;
position: relative;
top: 4px;
font-size: 24px;
color: #999;
transition: all 0.3s;
}
.dynamic-delete-button:hover {
color: #777;
}
.dynamic-delete-button[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
import { Form, DatePicker, TimePicker, Button } from 'choerodon-ui';
const FormItem = Form.Item;
const MonthPicker = DatePicker.MonthPicker;
const RangePicker = DatePicker.RangePicker;
class TimeRelatedForm extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, fieldsValue) => {
if (err) {
return;
}
// Should format date value before submit.
const rangeValue = fieldsValue['range-picker'];
const rangeTimeValue = fieldsValue['range-time-picker'];
const values = {
...fieldsValue,
'date-picker': fieldsValue['date-picker'].format('YYYY-MM-DD'),
'date-time-picker': fieldsValue['date-time-picker'].format('YYYY-MM-DD HH:mm:ss'),
'month-picker': fieldsValue['month-picker'].format('YYYY-MM'),
'range-picker': [rangeValue[0].format('YYYY-MM-DD'), rangeValue[1].format('YYYY-MM-DD')],
'range-time-picker': [
rangeTimeValue[0].format('YYYY-MM-DD HH:mm:ss'),
rangeTimeValue[1].format('YYYY-MM-DD HH:mm:ss'),
],
'time-picker': fieldsValue['time-picker'].format('HH:mm:ss'),
};
console.log('Received values of form: ', values);
});
};
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 8 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const config = {
rules: [{ type: 'object', required: true, message: 'Please select time!' }],
};
const rangeConfig = {
rules: [{ type: 'array', required: true, message: 'Please select time!' }],
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="DatePicker">
{getFieldDecorator('date-picker', config)(<DatePicker />)}
</FormItem>
<FormItem {...formItemLayout} label="DatePicker[showTime]">
{getFieldDecorator('date-time-picker', config)(
<DatePicker showTime format="YYYY-MM-DD HH:mm:ss" />,
)}
</FormItem>
<FormItem {...formItemLayout} label="MonthPicker">
{getFieldDecorator('month-picker', config)(<MonthPicker />)}
</FormItem>
<FormItem {...formItemLayout} label="RangePicker">
{getFieldDecorator('range-picker', rangeConfig)(<RangePicker />)}
</FormItem>
<FormItem {...formItemLayout} label="RangePicker[showTime]">
{getFieldDecorator('range-time-picker', rangeConfig)(
<RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />,
)}
</FormItem>
<FormItem {...formItemLayout} label="TimePicker">
{getFieldDecorator('time-picker', config)(<TimePicker />)}
</FormItem>
<FormItem
wrapperCol={{
xs: { span: 24, offset: 0 },
sm: { span: 16, offset: 8 },
}}
>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
const WrappedTimeRelatedForm = Form.create()(TimeRelatedForm);
ReactDOM.render(<WrappedTimeRelatedForm />, mountNode);
自定义或第三方的表单控件,也可以与 Form 组件一起使用。只要该组件遵循以下的约定:
提供受控属性
value
或其它与valuePropName
的值同名的属性。提供
onChange
事件或trigger
的值同名的事件。不能是函数式组件。
import { Form, Input, Select, Button } from 'choerodon-ui';
const FormItem = Form.Item;
const Option = Select.Option;
class PriceInput extends React.Component {
constructor(props) {
super(props);
const value = this.props.value || {};
this.state = {
number: value.number || 0,
currency: value.currency || 'rmb',
};
}
componentWillReceiveProps(nextProps) {
// Should be a controlled component.
if ('value' in nextProps) {
const value = nextProps.value;
this.setState(value);
}
}
handleNumberChange = e => {
const number = parseInt(e.target.value || 0, 10);
if (isNaN(number)) {
return;
}
if (!('value' in this.props)) {
this.setState({ number });
}
this.triggerChange({ number });
};
handleCurrencyChange = currency => {
if (!('value' in this.props)) {
this.setState({ currency });
}
this.triggerChange({ currency });
};
triggerChange = changedValue => {
// Should provide an event to pass value to Form.
const onChange = this.props.onChange;
if (onChange) {
onChange({ ...this.state, ...changedValue });
}
};
render() {
const { size } = this.props;
const state = this.state;
return (
<span>
<Input
type="text"
size={size}
value={state.number}
onChange={this.handleNumberChange}
style={{ width: '65%', marginRight: '3%', lineHeight: '1.7' }}
/>
<Select
value={state.currency}
size={size}
style={{ width: '32%' }}
onChange={this.handleCurrencyChange}
>
<Option value="rmb">RMB</Option>
<Option value="dollar">Dollar</Option>
</Select>
</span>
);
}
}
class Demo extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
checkPrice = (rule, value, callback) => {
if (value.number > 0) {
callback();
return;
}
callback('Price must greater than zero!');
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem label="Price">
{getFieldDecorator('price', {
initialValue: { number: 0, currency: 'rmb' },
rules: [{ validator: this.checkPrice }],
})(<PriceInput />)}
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
const WrappedDemo = Form.create()(Demo);
ReactDOM.render(<WrappedDemo />, mountNode);
通过使用 onFieldsChange
与 mapPropsToFields
,可以把表单的数据存储到上层组件或者 Redux、dva 中,更多可参考 rc-form 示例。
注意:mapPropsToFields
里面返回的表单域数据必须使用 Form.createFormField
包装。
import { Form, Input } from 'choerodon-ui';
const FormItem = Form.Item;
const CustomizedForm = Form.create({
onFieldsChange(props, changedFields) {
props.onChange(changedFields);
},
mapPropsToFields(props) {
return {
username: Form.createFormField({
...props.username,
value: props.username.value,
}),
};
},
onValuesChange(_, values) {
console.log(values);
},
})(props => {
const { getFieldDecorator } = props.form;
return (
<Form layout="inline">
<FormItem label="Username">
{getFieldDecorator('username', {
rules: [{ required: true, message: 'Username is required!' }],
})(<Input />)}
</FormItem>
</Form>
);
});
class Demo extends React.Component {
state = {
fields: {
username: {
value: 'benjycui',
},
},
};
handleFormChange = changedFields => {
this.setState(({ fields }) => ({
fields: { ...fields, ...changedFields },
}));
};
render() {
const fields = this.state.fields;
return (
<div>
<CustomizedForm {...fields} onChange={this.handleFormChange} />
<pre className="language-bash">{JSON.stringify(fields, null, 2)}</pre>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
使用 Form.create
处理后的表单具有自动收集数据并校验的功能,但如果您不需要这个功能,或者默认的行为无法满足业务需求,可以选择不使用 Form.create
并自行处理数据。
import { Form, InputNumber } from 'choerodon-ui';
const FormItem = Form.Item;
function validatePrimeNumber(number) {
if (number === 11) {
return {
validateStatus: 'success',
errorMsg: null,
};
}
return {
validateStatus: 'error',
errorMsg: 'The prime between 8 and 12 is 11!',
};
}
class RawForm extends React.Component {
state = {
number: {
value: 11,
},
};
handleNumberChange = value => {
this.setState({
number: {
...validatePrimeNumber(value),
value,
},
});
};
render() {
const formItemLayout = {
labelCol: { span: 7 },
wrapperCol: { span: 12 },
};
const number = this.state.number;
const tips =
'A prime is a natural number greater than 1 that has no positive divisors other than 1 and itself.';
return (
<Form>
<FormItem
{...formItemLayout}
label="Prime between 8 & 12"
validateStatus={number.validateStatus}
help={number.errorMsg || tips}
>
<InputNumber min={8} max={12} value={number.value} onChange={this.handleNumberChange} />
</FormItem>
</Form>
);
}
}
ReactDOM.render(<RawForm />, mountNode);
我们提供了 validateStatus
help
hasFeedback
等属性,你可以不需要使用 Form.create
和 getFieldDecorator
,自己定义校验的时机和内容。
validateStatus
: 校验状态,可选 'success', 'warning', 'error', 'validating'。hasFeedback
:用于给输入框添加反馈图标。help
:设置校验文案。
import {
Form,
Input,
DatePicker,
Col,
TimePicker,
Select,
Cascader,
InputNumber,
} from 'choerodon-ui';
const FormItem = Form.Item;
const Option = Select.Option;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
ReactDOM.render(
<Form>
<FormItem
{...formItemLayout}
label="Fail"
validateStatus="error"
help="Should be combination of numbers & alphabets"
>
<Input placeholder="unavailable choice" id="error" />
</FormItem>
<FormItem {...formItemLayout} label="Warning" validateStatus="warning">
<Input placeholder="Warning" id="warning" />
</FormItem>
<FormItem
{...formItemLayout}
label="Validating"
hasFeedback
validateStatus="validating"
help="The information is being validated..."
>
<Input placeholder="I'm the content is being validated" id="validating" />
</FormItem>
<FormItem {...formItemLayout} label="Success" hasFeedback validateStatus="success">
<Input placeholder="I'm the content" id="success" />
</FormItem>
<FormItem {...formItemLayout} label="Warning" hasFeedback validateStatus="warning">
<Input placeholder="Warning" id="warning" />
</FormItem>
<FormItem
{...formItemLayout}
label="Fail"
hasFeedback
validateStatus="error"
help="Should be combination of numbers & alphabets"
>
<Input placeholder="unavailable choice" id="error" />
</FormItem>
<FormItem {...formItemLayout} label="Success" hasFeedback validateStatus="success">
<DatePicker style={{ width: '100%' }} />
</FormItem>
<FormItem {...formItemLayout} label="Warning" hasFeedback validateStatus="warning">
<TimePicker style={{ width: '100%' }} />
</FormItem>
<FormItem {...formItemLayout} label="Error" hasFeedback validateStatus="error">
<Select defaultValue="1">
<Option value="1">Option 1</Option>
<Option value="2">Option 2</Option>
<Option value="3">Option 3</Option>
</Select>
</FormItem>
<FormItem
{...formItemLayout}
label="Validating"
hasFeedback
validateStatus="validating"
help="The information is being validated..."
>
<Cascader defaultValue={['1']} options={[]} />
</FormItem>
<FormItem label="inline" {...formItemLayout}>
<Col span={11}>
<FormItem validateStatus="error" help="Please select the correct date">
<DatePicker />
</FormItem>
</Col>
<Col span={2}>
<span style={{ display: 'inline-block', width: '100%', textAlign: 'center' }}>-</span>
</Col>
<Col span={11}>
<FormItem>
<DatePicker />
</FormItem>
</Col>
</FormItem>
<FormItem {...formItemLayout} label="Success" hasFeedback validateStatus="success">
<InputNumber style={{ width: '100%' }} />
</FormItem>
</Form>,
mountNode,
);
使用 setFieldsValue
来动态设置其他控件的值。
import { Form, Select, Input, Button } from 'choerodon-ui';
const FormItem = Form.Item;
const Option = Select.Option;
class App extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
handleSelectChange = value => {
console.log(value);
this.props.form.setFieldsValue({
note: `Hi, ${value === 'male' ? 'man' : 'lady'}!`,
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<Form onSubmit={this.handleSubmit}>
<FormItem label="Note" labelCol={{ span: 5 }} wrapperCol={{ span: 12 }}>
{getFieldDecorator('note', {
rules: [{ required: true, message: 'Please input your note!' }],
})(<Input />)}
</FormItem>
<FormItem label="Gender" labelCol={{ span: 5 }} wrapperCol={{ span: 12 }}>
{getFieldDecorator('gender', {
rules: [{ required: true, message: 'Please select your gender!' }],
})(
<Select
placeholder="Select a option and change input text above"
onChange={this.handleSelectChange}
>
<Option value="male">male</Option>
<Option value="female">female</Option>
</Select>,
)}
</FormItem>
<FormItem wrapperCol={{ span: 12, offset: 5 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
const WrappedApp = Form.create()(App);
ReactDOM.render(<WrappedApp />, mountNode);
表单有三种布局。
import { Form, Input, Button, Radio } from 'choerodon-ui';
const FormItem = Form.Item;
class FormLayoutDemo extends React.Component {
constructor() {
super();
this.state = {
formLayout: 'horizontal',
};
}
handleFormLayoutChange = e => {
this.setState({ formLayout: e.target.value });
};
render() {
const { formLayout } = this.state;
const formItemLayout =
formLayout === 'horizontal'
? {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
}
: null;
const buttonItemLayout =
formLayout === 'horizontal'
? {
wrapperCol: { span: 14, offset: 4 },
}
: null;
return (
<div>
<Form layout={formLayout}>
<FormItem label="Form Layout" {...formItemLayout}>
<Radio.Group defaultValue="horizontal" onChange={this.handleFormLayoutChange}>
<Radio.Button value="horizontal">Horizontal</Radio.Button>
<Radio.Button value="vertical">Vertical</Radio.Button>
<Radio.Button value="inline">Inline</Radio.Button>
</Radio.Group>
</FormItem>
<FormItem label="Field A" {...formItemLayout}>
<Input placeholder="input placeholder" />
</FormItem>
<FormItem label="Field B" {...formItemLayout}>
<Input placeholder="input placeholder" />
</FormItem>
<FormItem {...buttonItemLayout}>
<Button type="primary">Submit</Button>
</FormItem>
</Form>
</div>
);
}
}
ReactDOM.render(<FormLayoutDemo />, mountNode);
根据不同情况执行不同的校验规则。
import { Form, Input, Button, Checkbox } from 'choerodon-ui';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 8 },
};
const formTailLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 8, offset: 4 },
};
class DynamicRule extends React.Component {
state = {
checkNick: false,
};
check = () => {
this.props.form.validateFields(err => {
if (!err) {
console.info('success');
}
});
};
handleChange = e => {
this.setState(
{
checkNick: e.target.checked,
},
() => {
this.props.form.validateFields(['nickname'], { force: true });
},
);
};
render() {
const { getFieldDecorator } = this.props.form;
return (
<div>
<FormItem {...formItemLayout} label="Name">
{getFieldDecorator('username', {
rules: [
{
required: true,
message: 'Please input your name',
},
],
})(<Input placeholder="Please input your name" />)}
</FormItem>
<FormItem {...formItemLayout} label="Nickname">
{getFieldDecorator('nickname', {
rules: [
{
required: this.state.checkNick,
message: 'Please input your nickname',
},
],
})(<Input placeholder="Please input your nickname" />)}
</FormItem>
<FormItem {...formTailLayout}>
<Checkbox value={this.state.checkNick} onChange={this.handleChange}>
Nickname is required
</Checkbox>
</FormItem>
<FormItem {...formTailLayout}>
<Button type="primary" onClick={this.check}>
Check
</Button>
</FormItem>
</div>
);
}
}
const WrappedDynamicRule = Form.create()(DynamicRule);
ReactDOM.render(<WrappedDynamicRule />, mountNode);
以上演示没有出现的表单控件对应的校验演示。
import {
Form,
Select,
InputNumber,
Switch,
Radio,
Slider,
Button,
Upload,
Icon,
Rate,
} from 'choerodon-ui';
const FormItem = Form.Item;
const Option = Select.Option;
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
class Demo extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
normFile = e => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="Plain Text">
<span className="c7n-form-text">China</span>
</FormItem>
<FormItem {...formItemLayout} label="Select" hasFeedback>
{getFieldDecorator('select', {
rules: [{ required: true, message: 'Please select your country!' }],
})(
<Select placeholder="Please select a country">
<Option value="china">China</Option>
<Option value="use">U.S.A</Option>
</Select>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Select[multiple]">
{getFieldDecorator('select-multiple', {
rules: [
{ required: true, message: 'Please select your favourite colors!', type: 'array' },
],
})(
<Select mode="multiple" placeholder="Please select favourite colors">
<Option value="red">Red</Option>
<Option value="green">Green</Option>
<Option value="blue">Blue</Option>
</Select>,
)}
</FormItem>
<FormItem {...formItemLayout} label="InputNumber">
{getFieldDecorator('input-number', { initialValue: 3 })(<InputNumber min={1} max={10} />)}
<span className="c7n-form-text"> machines</span>
</FormItem>
<FormItem {...formItemLayout} label="Switch">
{getFieldDecorator('switch', { valuePropName: 'checked' })(<Switch />)}
</FormItem>
<FormItem {...formItemLayout} label="Slider">
{getFieldDecorator('slider')(
<Slider marks={{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F' }} />,
)}
</FormItem>
<FormItem {...formItemLayout} label="Radio.Group">
{getFieldDecorator('radio-group')(
<RadioGroup>
<Radio value="a">item 1</Radio>
<Radio value="b">item 2</Radio>
<Radio value="c">item 3</Radio>
</RadioGroup>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Radio.Button">
{getFieldDecorator('radio-button')(
<RadioGroup>
<RadioButton value="a">item 1</RadioButton>
<RadioButton value="b">item 2</RadioButton>
<RadioButton value="c">item 3</RadioButton>
</RadioGroup>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Rate">
{getFieldDecorator('rate', {
initialValue: 3.5,
})(<Rate />)}
</FormItem>
<FormItem {...formItemLayout} label="Upload" extra="longgggggggggggggggggggggggggggggggggg">
{getFieldDecorator('upload', {
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
})(
<Upload name="logo" action="/upload.do" listType="picture">
<Button>
<Icon type="upload" /> Click to upload
</Button>
</Upload>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Dragger">
<div className="dropbox">
{getFieldDecorator('dragger', {
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
})(
<Upload.Dragger name="files" action="/upload.do">
<p className="c7n-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="c7n-upload-text">Click or drag file to this area to upload</p>
<p className="c7n-upload-hint">Support for a single or bulk upload.</p>
</Upload.Dragger>,
)}
</div>
</FormItem>
<FormItem wrapperCol={{ span: 12, offset: 6 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
const WrappedDemo = Form.create()(Demo);
ReactDOM.render(<WrappedDemo />, mountNode);
#components-form-demo-validate-other .dropbox {
height: 180px;
line-height: 1.5;
}
API
Form
更多示例参考 rc-form。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
form | 经 Form.create() 包装过的组件会自带 this.props.form 属性,直接传给 Form 即可。1.7.0 之后无需设置 | object | 无 |
hideRequiredMark | 隐藏所有表单项的必选标记 | Boolean | false |
layout | 表单布局(2.8 之后支持) | 'horizontal'|'vertical'|'inline' | 'horizontal' |
onSubmit | 数据验证成功后回调事件 | Function(e:Event) |
Form.create(options)
使用方式如下:
class CustomizedForm extends React.Component {}
CustomizedForm = Form.create({})(CustomizedForm);
options
的配置项如下。
参数 | 说明 | 类型 |
---|---|---|
mapPropsToFields | 把父组件的属性映射到表单项上(如:把 Redux store 中的值读出),需要对返回值中的表单域数据用 Form.createFormField 标记 | (props) => Object{ fieldName: FormField { value } } |
validateMessages | 默认校验信息,可用于把默认错误信息改为中文等,格式与 newMessages 返回值一致 | Object { nested.path: String } |
onFieldsChange | 当 Form.Item 子节点的值发生改变时触发,可以把对应的值转存到 Redux store | Function(props, fields) |
onValuesChange | 任一表单域的值发生改变时的回调 | (props, values) => void |
经过 Form.create
包装的组件将会自带 this.props.form
属性,this.props.form
提供的 API 如下:
注意:使用
getFieldsValue
getFieldValue
setFieldsValue
等时,应确保对应的 field 已经用getFieldDecorator
注册过了。
方法 | 说明 | 类型 |
---|---|---|
getFieldDecorator | 用于和表单进行双向绑定,详见下方描述 | |
getFieldError | 获取某个输入控件的 Error | Function(name) |
getFieldsError | 获取一组输入控件的 Error ,如不传入参数,则获取全部组件的 Error | Function([names: string[]]) |
getFieldsValue | 获取一组输入控件的值,如不传入参数,则获取全部组件的值 | Function([fieldNames: string[]]) |
getFieldValue | 获取一个输入控件的值 | Function(fieldName: string) |
isFieldsTouched | 判断是否任一输入控件经历过 getFieldDecorator 的值收集时机 options.trigger | (names?: string[]) => boolean |
isFieldTouched | 判断一个输入控件是否经历过 getFieldDecorator 的值收集时机 options.trigger | (name: string) => boolean |
isFieldValidating | 判断一个输入控件是否在校验状态 | Function(name) |
resetFields | 重置一组输入控件的值(为 initialValue )与状态,如不传入参数,则重置所有组件 | Function([names: string[]]) |
setFields | 设置一组输入控件的值与 Error。 代码 | Function({ fieldName: { value: any, errors: Error } }) |
setFieldsValue | 设置一组输入控件的值(注意:不要在 componentWillReceiveProps 内使用,否则会导致死循环 | Function({ fieldName: value } |
validateFields | 校验并获取一组输入域的值与 Error,若 fieldNames 参数为空,则校验全部组件 | Function([fieldNames: string[]], options: object, callback: Function(errors, values, modify)) |
validateFieldsAndScroll | 与 validateFields 相似,但校验完后,如果校验不通过的菜单域不在可见范围内,则自动滚动进可见范围 | 参考 validateFields |
isModifiedFields | 判断一组输入控件的值是否有修改 | (names?: string[]) => boolean |
isModifiedField | 判断一个输入控件的值是否有修改 | (name: string) => boolean |
this.props.form.validateFields/validateFieldsAndScroll([fieldNames: string[]], options: object, callback: Function(errors, values))
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
options.first | 若为 true,则每一表单域的都会在碰到第一个失败了的校验规则后停止校验 | boolean | false |
options.firstFields | 指定表单域会在碰到第一个失败了的校验规则后停止校验 | String[] | [] |
options.force | 对已经校验过的表单域,在 validateTrigger 再次被触发时是否再次校验 | boolean | false |
options.scroll | 定义 validateFieldsAndScroll 的滚动行为,详细配置见 dom-scroll-into-view config | Object | {} |
Form.createFormField
用于标记 mapPropsToFields
返回的表单域数据,例子。
this.props.form.getFieldDecorator(id, options)
经过 getFieldDecorator
包装的控件,表单控件会自动添加 value
(或 valuePropName
指定的其他属性) onChange
(或 trigger
指定的其他属性),数据同步将被 Form 接管,这会导致以下结果:
你不再需要也不应该用
onChange
来做同步,但还是可以继续监听onChange
等事件。你不能用控件的
value
defaultValue
等属性来设置表单域的值,默认值可以用getFieldDecorator
里的initialValue
。你不应该用
setState
,可以使用this.props.form.setFieldsValue
来动态改变表单值。
特别注意
getFieldDecorator
不能用于装饰纯函数组件。如果使用的是
react@<15.3.0
,则getFieldDecorator
调用不能位于纯函数组件中: https://github.com/facebook/react/pull/6534
getFieldDecorator(id, options) 参数
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
id | 必填输入控件唯一标志。支持嵌套式的写法。 | string | |
options.getValueFromEvent | 可以把 onChange 的参数(如 event)转化为控件的值 | function(..args) | reference |
options.initialValue | 子节点的初始值,类型、可选值均由子节点决定(注意:由于内部校验时使用 === 判断是否变化,建议使用变量缓存所需设置的值而非直接使用字面量)) | ||
options.normalize | 转换默认的 value 给控件,一个选择全部的例子 | function(value, prevValue, allValues): any | - |
options.rules | 校验规则,参考下方文档 | object[] | |
options.trigger | 收集子节点的值的时机 | string | 'onChange' |
options.validateFirst | 当某一规则校验不通过时,是否停止剩下的规则的校验 | boolean | false |
options.validateTrigger | 校验子节点值的时机 | string|string[] | 'onChange' |
options.valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
更多参数请查看 rc-form option。
Form.Item
注意:
一个 Form.Item 建议只放一个被 getFieldDecorator 装饰过的 child,当有多个被装饰过的 child 时,
help
required
validateStatus
无法自动生成。2.2.0
之前,只有当表单域为 Form.Item 的子元素时,才会自动生成help
required
validateStatus
,嵌套情况需要自行设置。
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
colon | 配合 label 属性使用,表示是否显示 label 后面的冒号 | boolean | true |
extra | 额外的提示信息,和 help 类似,当需要错误信息和提示文案同时出现时,可以使用这个。 | string|ReactNode | |
hasFeedback | 配合 validateStatus 属性使用,展示校验状态图标,建议只配合 Input 组件使用 | boolean | false |
help | 提示信息,如不设置,则会根据校验规则自动生成 | string|ReactNode | |
label | label 标签的文本 | string|ReactNode | |
labelCol | label 标签布局,同 <Col> 组件,设置 span offset 值,如 {span: 3, offset: 12} 或 sm: {span: 3, offset: 12} | object | |
required | 是否必填,如不设置,则会根据校验规则自动生成 | boolean | false |
validateStatus | 校验状态,如不设置,则会根据校验规则自动生成,可选:'success' 'warning' 'error' 'validating' | string | |
wrapperCol | 需要为输入控件设置布局样式时,使用该属性,用法同 labelCol | object |
校验规则
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
enum | 枚举类型 | string | - |
len | 字段长度 | number | - |
max | 最大长度 | number | - |
message | 校验文案 | string | - |
min | 最小长度 | number | - |
pattern | 正则表达式校验 | RegExp | - |
required | 是否必选 | boolean | false |
transform | 校验前转换字段值 | function(value) => transformedValue:any | - |
type | 内建校验类型,可选项 | string | 'string' |
validator | 自定义校验(注意,callback 必须被调用) | function(rule, value, callback) | - |
whitespace | 必选时,空格是否会被视为错误 | boolean | false |
更多高级用法可研究 async-validator。
在 TypeScript 中使用
import { Form } from 'choerodon-ui';
import { FormComponentProps } from 'choerodon-ui/lib/form';
interface UserFormProps extends FormComponentProps {
age: number;
name: string;
}
class UserForm extends React.Component<UserFormProps, any> {
}