├── .babelrc ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── package.json ├── src ├── form.js └── index.js ├── test ├── entry.js └── index.html └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | lib/ 4 | local/ 5 | node_modules/ 6 | npm-debug.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | src/ 3 | local/ 4 | node_modules/ 5 | test/ 6 | npm-debug.log 7 | webpack.config.js -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.0 2 | - now Group, Array and fields will receive validation state and message, thus creating more flexible components is possible. 3 | 4 | ## 0.4.2 5 | - update readme and keywords on npm 6 | 7 | ## 0.4.1 8 | - update readme on npm 9 | 10 | ## 0.4.0 11 | - build full value using cleanValue method of each field to prevent dirty value after schema changing 12 | - compute validation result before updating 13 | - return an editable value in onSubmit and submit methods 14 | - return validation summary and detail in onChange method of form 15 | - support set form type by props.type 16 | 17 | ## 0.3.0 18 | - support change schema 19 | 20 | ## 0.2.0 21 | - support full value cache 22 | - support validate against full form value, aka, change validate(value) to validate(value, formValue) 23 | - support ignore value of a field in a group 24 | 25 | ## 0.1.0 26 | first release 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RF Form 2 | 3 | build react form with validations in a better way. 4 | 5 | **Note: This project is inactively maintained, you can have a try to the great alternative [uniforms](https://github.com/vazco/uniforms)** 6 | 7 | ## Features 8 | - build React form with validations easily 9 | - schema based 10 | - support recursive structures 11 | - support circle schema definition 12 | - support connected forms 13 | - support manipulations of array of fields 14 | - support top level and individual controls of readOnly, disabled 15 | - enable validations of fields automatically 16 | - support extensions of form components (wrapper, group, array, fields) 17 | 18 | ## Demo 19 | [Demo Site](http://shakingmap.github.io/rf-form) 20 | 21 | ## Installation 22 | - install React via npm 23 | - install this package via npm: `npm install rf-form --save` 24 | - optionally install some [form components suit](https://github.com/ShakingMap/rf-form#components-suits-list) 25 | 26 | ## Basic Usage 27 | 28 | For more usages, please refer to the [Demo Site](http://shakingmap.github.io/rf-form). 29 | 30 | First, import *Form* and any *Form Components Suit* you choose. 31 | 32 | import Form from 'rf-form'; 33 | import * as buildOptions from 'rf-bootstrap3'; 34 | 35 | Then, you can optionally set the *Form Components Suit* as default props of the *Form*, or you can pass it as a prop when you render the form. 36 | 37 | Form.defaultProps.buildOptions = buildOptions; 38 | 39 | Then, define your [schema](https://github.com/ShakingMap/rf-form#field-schema). 40 | 41 | const schema = { 42 | name: { 43 | type: 'Text', 44 | label: 'Name', 45 | validate(v) { 46 | if (!v) return 'Name is required.' 47 | } 48 | }, 49 | birthday: { 50 | type: 'Date', 51 | label: 'Birthday' 52 | }, 53 | sex: { 54 | type: 'RadioGroup', 55 | label: 'Sex', 56 | options: { 57 | items: { 58 | male: 'Male', 59 | female: 'Female', 60 | unknown: {label: 'Unknown', disabled: true} 61 | } 62 | }, 63 | validate(v) { 64 | if (!v) return 'Sex is required.' 65 | } 66 | }, 67 | friends: { 68 | label: 'Friends', 69 | array: { 70 | type: 'Text', 71 | label: 'Friend Name' 72 | }, 73 | validate(v) { 74 | if (v.length > 3) return 'You have too much friends.' 75 | } 76 | }, 77 | account: { 78 | label: 'Account', 79 | group: { 80 | username: { 81 | type: 'Text', 82 | label: 'Username', 83 | validate(v) {if (!v) return 'Username is required.'} 84 | }, 85 | password: { 86 | type: 'Password', 87 | label: 'Password', 88 | validate(v) {if (!v || v.length < 6) return 'Password length must be >= 6.'} 89 | }, 90 | confirmPassword: { 91 | type: 'Password', 92 | label: 'Confirm Password', 93 | validate(v, fv /*form value*/) { 94 | if (v !== fv.account.password) return 'Password does not match.' 95 | } 96 | } 97 | } 98 | } 99 | }; 100 | 101 | Finally, render the form 102 | 103 | class TestPage extends React.Component { 104 | render() { 105 | return
console.log({value, summary, detail}) 108 | 109 | // you can specify value and onChange props to make the form work in controlled mode. 110 | // value: ... 111 | // onChange: (value, summary, detail)=> ... 112 | }}> 113 | 114 |
115 | } 116 | } 117 | 118 | ## APIs 119 | 120 | If you are not interested in creating form component, you can only have a look at the apis of *Form*. 121 | 122 | ### Common Concepts 123 | - validation state - one of 'success', 'warning', 'error' or falsy 124 | 125 | ### Form 126 | #### Props 127 | - schema - the *group field* of a [field schema](https://github.com/ShakingMap/rf-form#field-schema) 128 | - buildOptions - an object as {Wrapper, Group, Array, fields}, being used to build the form, often provided by some *form components suit package*. 129 | - value - if undefined, the form will be uncontrolled. 130 | - onChange - func(value, summary, details), summary is {success: count, warning: count, error: count}, details are all validation results. 131 | - onSubmit - func(value, summary, details), summary is {success: count, warning: count, error: count}, details are all validation results. 132 | - subForms - func(), return a group of sub forms in an object, such as {form1: this.refs.form1, form2: this.refs.form2, ...}. 133 | - readOnly - bool 134 | - disabled - bool 135 | - enableValidation - bool or 'auto', default to 'auto', which will enable validation of a field if the onChange of the field is triggered 136 | - type - string, default to 'form' 137 | 138 | ### Wrapper 139 | A Wrapper is a form component which is responsible for rendering label, field component and validation message. 140 | #### Props 141 | - id - field id, usually used as *htmlFor* prop of label 142 | - label - optional string 143 | - validationState - see *validation state* of common concepts 144 | - validationMessage - string 145 | - children - field 146 | 147 | ### Group 148 | A Group is a form component which is responsible for organizing a group of fields 149 | #### Props 150 | - children - field 151 | - validationState - see *validation state* of common concepts 152 | - validationMessage - string 153 | - readOnly - usually no effect 154 | - disabled - usually no effect 155 | 156 | ### Array 157 | An Array is a form component which is responsible for organizing an array of same fields 158 | #### Props 159 | - children - field 160 | - validationState - see *validation state* of common concepts 161 | - validationMessage - string 162 | - readOnly - usually no effect 163 | - disabled - the array manipulation operations should be disabled 164 | - onInsert - func(index) 165 | - onRemove - func(index) 166 | - onMove - func(from, to) 167 | 168 | ### Field 169 | A field is a form component which is responsible for managing the this field 170 | #### Props 171 | - id - field id, usually used as *id* props of inner input 172 | - validationState - see *validation state* of common concepts 173 | - validationMessage - string 174 | - value - any value this field needs. 175 | - onChange - func(value, event) 176 | - readOnly - bool, usually implemented by text field 177 | - disabled - bool, field should be disabled 178 | - ... - field component can define other props it needs, passed in from the corresponding schema.options 179 | 180 | #### Methods 181 | - cleanValue - func(value, options): compatibleValue. return a value that is compatible with this field. 182 | 183 | ## Field Schema 184 | A field schema is an object of following keys: 185 | 186 | - type - string or field component. if string, it will be mapped into a field component by form.props.buildOptions.fields. if this schema has array or group key, type key can be omitted 187 | - wrapper - optional string or wrapper component. if string, it will be mapped into a wrapper component by form.props.buildOptions.fields 188 | - label - optional string 189 | - options - all options will be spread to the props of corresponding form component. you should only set allowed options such as `readOnly`, `disabled` and any alllowed options defined by specific field component(usually addressed in its doc). 190 | - validate - optional func(value, formValue): result. standard result format is {state: validationState, message: validationMessage}. if result is not standard, it will be converted as below: 191 | - falsy -> {state: 'success', message: ''} 192 | - string -> {state: 'error', message: string} 193 | - array -> {state: array[0], message: array[1]} 194 | - array - optional *field schema*. if exists, this schema indicates an array field. 195 | - group - optional object as {key: *field schema*}. if exists, this schema indicates a group field. 196 | - ignoreValue - optional bool. only available for field in a group. if true, the value of this field won't be included in the form value and form validation on submit 197 | 198 | ## Components Suits List 199 | - [rf-bootstrap3](https://github.com/ShakingMap/rf-bootstrap3) - form components suit for bootstrap3 200 | - [rf-materialize](https://github.com/ShakingMap/rf-materialize) - form components suit for materialize 201 | - [rf-material-ui](https://github.com/ShakingMap/rf-material-ui) - form components suit for material-ui 202 | 203 | ## Q&A 204 | ### How to connect forms? 205 | For example 206 | 207 |
{ 209 | return { 210 | form1: this.refs.form1, 211 | form2: this.refs.form2 212 | } 213 | }, 214 | onSubmit: (value, summary, detail)=> console.log({value, summary, detail}) 215 | }}> 216 | 221 | 226 | 227 |
228 | 229 | You can separate your form into many sub forms and layout them in this way. 230 | 231 | ## License 232 | ISC 233 | 234 | ## Let's Build It Together! 235 | If you like this project, welcome to give any helps and supports! Thanks! 236 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rf-form", 3 | "version": "0.5.0", 4 | "description": "rf form: build react form with validations in a better way.", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "node node_modules/webpack/bin/webpack.js --progress --colors --watch -d", 8 | "build": "node node_modules/babel-cli/bin/babel ./src --out-dir ./lib --source-maps", 9 | "autobuild": "node node_modules/babel-cli/bin/babel ./src --out-dir ./lib --source-maps --watch", 10 | "prepublish": "npm run build" 11 | }, 12 | "author": "zhaoyao91 ", 13 | "license": "ISC", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ShakingMap/rf-form.git" 17 | }, 18 | "dependencies": { 19 | "lodash": "^4.12.0" 20 | }, 21 | "peerDependencies": { 22 | "react": ">=0.13.0" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.8.0", 26 | "babel-core": "^6.8.0", 27 | "babel-loader": "^6.2.4", 28 | "babel-preset-es2015": "^6.6.0", 29 | "babel-preset-react": "^6.5.0", 30 | "babel-preset-stage-2": "^6.5.0", 31 | "bootstrap": "^3.3.6", 32 | "css-loader": "^0.23.1", 33 | "file-loader": "^0.8.5", 34 | "react": "^15.0.2", 35 | "react-dom": "^15.0.2", 36 | "rf-bootstrap3": "^0.3.0", 37 | "source-map-loader": "^0.1.5", 38 | "style-loader": "^0.13.1", 39 | "url-loader": "^0.5.7", 40 | "webpack": "^1.13.0", 41 | "webpack-dev-server": "^1.14.1" 42 | }, 43 | "keywords": ["react", "form", "rf-form"] 44 | } 45 | -------------------------------------------------------------------------------- /src/form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import _ from 'lodash'; 3 | 4 | /** 5 | * Some Conceptions 6 | * 7 | * build options: {fields, Wrapper, Group, Array} 8 | * 9 | * validation: func(value, formValue, form): result 10 | * standard result format is {state, message} 11 | * if result is 12 | * falsy -> {state: 'success', message: ''} 13 | * string -> {state: 'error', message: string} 14 | * array -> {state: array[0], message: array[1]} 15 | */ 16 | 17 | /** 18 | * Notes 19 | * 20 | * onChange is modified to pass enable validation state up. 21 | */ 22 | 23 | const propTypes = { 24 | schema: React.PropTypes.object.isRequired, 25 | buildOptions: React.PropTypes.object.isRequired, 26 | 27 | value: React.PropTypes.object, 28 | 29 | // func(value, summary, detail) 30 | onChange: React.PropTypes.func, 31 | 32 | // func(value, summary, detail) 33 | onSubmit: React.PropTypes.func, 34 | 35 | // func(): {key: form} 36 | subForms: React.PropTypes.func, 37 | 38 | readOnly: React.PropTypes.bool, 39 | 40 | disabled: React.PropTypes.bool, 41 | 42 | enableValidation: React.PropTypes.any, 43 | 44 | type: React.PropTypes.string 45 | }; 46 | 47 | const defaultProps = { 48 | schema: {}, 49 | buildOptions: {}, 50 | // value: {}, // to support uncontrolled form, default value should be disabled 51 | onChange(){}, 52 | onSubmit(){}, 53 | readOnly: false, 54 | disabled: false, 55 | enableValidation: 'auto', 56 | type: 'form' 57 | }; 58 | 59 | class Form extends React.Component { 60 | constructor(props) { 61 | super(props); 62 | this.state = { 63 | enableValidation: false 64 | } 65 | } 66 | 67 | componentWillMount() { 68 | this.id = Math.random() + ''; 69 | this.enableValidationState = null; 70 | this.fullValue = getFullValue(this.getFormSchema(), this.getValue(), this.props.buildOptions.fields); 71 | this.validationResult = getValidationResult(this.getFormSchema(), this.fullValue, this.fullValue); 72 | } 73 | 74 | componentWillUpdate(nextProps, nextState) { 75 | this.fullValue = getFullValue(this.getFormSchema(nextProps), this.getValue(nextProps, nextState), nextProps.buildOptions.fields); 76 | this.validationResult = getValidationResult(this.getFormSchema(nextProps), this.fullValue, this.fullValue) 77 | } 78 | 79 | render() { 80 | const {onChange, buildOptions, enableValidation, children, readOnly, disabled, type} = this.props; 81 | const schema = this.getFormSchema(); 82 | 83 | return React.createElement(type, { 84 | onSubmit: this.onSubmit.bind(this) 85 | }, 86 | this.getRenderNode({ 87 | id: this.id, 88 | schema, 89 | value: this.fullValue, 90 | validation: this.validationResult.validation, 91 | enableValidationState: this.enableValidationState, 92 | onChange: (v, e, evs)=> { 93 | this.enableValidationState = evs; 94 | if (!this.isControlled()) this.setState({value: v}); 95 | const validationResult = getValidationResult(this.getFormSchema(), v, v); 96 | onChange(v, validationResult.summary, validationResult.validation); 97 | }, 98 | buildOptions, 99 | enableValidation, 100 | readOnly, disabled 101 | }), 102 | children 103 | ); 104 | } 105 | 106 | getFormSchema(props) { 107 | props = props || this.props; 108 | return { 109 | type: EmptyGroup, 110 | wrapper: EmptyWrapper, 111 | group: props.schema 112 | } 113 | } 114 | 115 | getRenderNode({id, schema, value, validation, onChange, enableValidationState, buildOptions, enableValidation, readOnly, disabled}) { 116 | // pre-process options 117 | const Wrapper = typeof schema.wrapper === 'string' ? buildOptions.fields[schema.wrapper] : (schema.wrapper || buildOptions.Wrapper); 118 | const options = schema.options || {}; 119 | enableValidationState = enableValidationState || {enabled: false, array: [], group: {}}; 120 | const localEnableValidation = enableValidation === 'auto' ? 121 | (this.state.enableValidation || enableValidationState.enabled) 122 | : 123 | enableValidation; 124 | const localReadOnly = schema.readOnly || readOnly; 125 | const localDisabled = schema.disabled || disabled; 126 | const validationState = localEnableValidation ? validation.state : ''; 127 | const validationMessage = localEnableValidation ? validation.message : ''; 128 | 129 | // build node; 130 | let node = null; 131 | if (schema.array) { 132 | const Node = typeof schema.type === 'string' ? buildOptions.fields[schema.type] : (schema.type || buildOptions.Array); 133 | const validationStateForActiveArray = { 134 | enabled: true, 135 | array: enableValidationState.array || [], 136 | group: enableValidationState.group || {} 137 | }; 138 | const children = _.map(value, (subValue, index)=>this.getRenderNode({ 139 | id: id + '.' + index, 140 | schema: schema.array, 141 | value: subValue, 142 | validation: validation.array[index], 143 | enableValidationState: enableValidationState.array[index], 144 | buildOptions, 145 | onChange: (v, e, evs)=> onChange( 146 | value.slice(0, index).concat([v], value.slice(index + 1)), 147 | e, 148 | { 149 | enabled: true, 150 | array: enableValidationState.array.slice(0, index).concat([evs], enableValidationState.array.slice(index + 1)), 151 | group: enableValidationState.group || {} 152 | } 153 | ), 154 | enableValidation, readOnly, disabled 155 | })); 156 | node = onChange(value.slice(0, index).concat(null, value.slice(index)), null, validationStateForActiveArray), 161 | onRemove: (index)=> onChange(value.slice(0, index).concat(value.slice(index + 1)), null, validationStateForActiveArray), 162 | onMove: (from, to)=> onChange( 163 | from < to ? 164 | value.slice(0, from).concat(value.slice(from + 1, to + 1), [value[from]], value.slice(to + 1)) 165 | : 166 | value.slice(0, to).concat([value[from]], value.slice(to, from), value.slice(from + 1)) 167 | , null, validationStateForActiveArray), 168 | readOnly: localReadOnly, 169 | disabled: localDisabled 170 | }}/> 171 | } 172 | else if (schema.group) { 173 | const Node = typeof schema.type === 'string' ? buildOptions.fields[schema.type] : (schema.type || buildOptions.Group); 174 | const children = _.map(schema.group, (subSchema, key)=>this.getRenderNode({ 175 | id: id + '.' + key, 176 | schema: subSchema, 177 | value: value[key], 178 | validation: validation.group[key], 179 | enableValidationState: enableValidationState.group[key], 180 | buildOptions, 181 | onChange: (v, e, evs)=> onChange( 182 | _.assign({}, value, {[key]: v}), 183 | e, 184 | { 185 | enabled: true, 186 | group: _.assign({}, enableValidationState.group, {[key]: evs}), 187 | array: enableValidationState.array || [] 188 | } 189 | ), 190 | enableValidation, readOnly, disabled 191 | })); 192 | node = 199 | } 200 | else { 201 | const Node = typeof schema.type === 'string' ? buildOptions.fields[schema.type] : schema.type; 202 | node = onChange(v, e, { 205 | enabled: true, 206 | array: enableValidationState.array || [], 207 | group: enableValidationState.group || {} 208 | }), 209 | validationState, 210 | validationMessage, 211 | readOnly: localReadOnly, 212 | disabled: localDisabled 213 | }}/> 214 | } 215 | 216 | // build wrapper; 217 | return 225 | } 226 | 227 | onSubmit(e) { 228 | e.preventDefault(); 229 | this.submit(); 230 | } 231 | 232 | isControlled(props) { 233 | props = props || this.props; 234 | return props.value !== undefined; 235 | } 236 | 237 | getValue(props, state) { 238 | props = props || this.props; 239 | state = state || this.state; 240 | return this.isControlled(props) ? props.value : state.value; 241 | } 242 | 243 | // public 244 | submit() { 245 | if (!this.state.enableValidation) this.setState({enableValidation: true}); // update only if this state changed to prevent unused update 246 | let result = { 247 | value: getFullValue(this.getFormSchema(), this.getValue(), this.props.buildOptions.fields), 248 | summary: this.validationResult.summary, 249 | validation: this.validationResult.validation 250 | }; 251 | if (this.props.subForms) { 252 | const subForms = this.props.subForms(); 253 | result = _.reduce(subForms, (result, form, key)=> { 254 | const subResult = form.submit(); 255 | return { 256 | value: _.assign({}, result.value, {[key]: subResult.value}), 257 | summary: _.assignWith({}, result.summary, subResult.summary, (v1, v2)=>(v1 || 0) + (v2 || 0)), 258 | validation: _.assign({}, result.validation, {group: _.assign({[key]: subResult.validation}, result.validation.group)}) 259 | } 260 | }, result) 261 | } 262 | this.props.onSubmit(result.value, result.summary, result.validation); 263 | return result; 264 | } 265 | } 266 | 267 | Form.propTypes = propTypes; 268 | Form.defaultProps = defaultProps; 269 | 270 | export default Form; 271 | 272 | const EmptyWrapper = ({children})=> { 273 | return
{children}
; 274 | }; 275 | 276 | const EmptyGroup = ({children})=> { 277 | return
{children}
; 278 | }; 279 | 280 | const validate = (validate, value, formValue)=> { 281 | if (!validate) return {state: '', message: ''}; 282 | else { 283 | const result = validate(value, formValue); 284 | if (!result) return {state: 'success', message: ''}; 285 | else if (typeof result === 'string') return {state: 'error', message: result}; 286 | else if (Array.isArray(result)) return {state: result[0], message: result[1]}; 287 | else return result; 288 | } 289 | }; 290 | 291 | /** 292 | * build value with prop default value 293 | * left node -> null 294 | * group -> {key: fullValue} 295 | * array -> [] 296 | */ 297 | const getFullValue = (schema, value, fields)=> { 298 | if (schema.group) { 299 | value = (typeof value === 'object' && value) || {}; 300 | return _(schema.group).omitBy(subSchema=>subSchema.ignoreValue).mapValues((subSchema, key)=>getFullValue(subSchema, value[key], fields)).value(); 301 | } 302 | else if (schema.array) { 303 | value = (Array.isArray(value) && value) || []; 304 | return _.map(value, (subValue, index)=>getFullValue(schema.array, subValue, fields)) 305 | } 306 | else return getField(schema, fields).cleanValue(value === undefined ? null : value, schema.options || {}); 307 | }; 308 | 309 | const getValidationResult = (schema, value, formValue)=> { 310 | const validation = validate(schema.validate, value, formValue); 311 | const summary = validation.state ? {[validation.state]: 1} : {}; 312 | 313 | if (schema.array) { 314 | value = value || []; 315 | return _.reduce(value, (result, subValue, index)=> { 316 | const subValidationData = getValidationResult(schema.array, subValue, formValue); 317 | return { 318 | summary: _.assignWith({}, result.summary, subValidationData.summary, (v1, v2)=> (v1 || 0) + (v2 || 0)), 319 | validation: _.assign({}, result.validation, {array: result.validation.array.concat([subValidationData.validation])}) 320 | } 321 | }, { 322 | summary, 323 | validation: _.assign({array: []}, validation) 324 | }) 325 | } 326 | else if (schema.group) { 327 | value = value || {}; 328 | return _.reduce(schema.group, (result, subSchema, key)=> { 329 | if (subSchema.ignoreValue) return result; 330 | const subValidationData = getValidationResult(subSchema, value[key], formValue); 331 | return { 332 | summary: _.assignWith({}, result.summary, subValidationData.summary, (v1, v2)=> (v1 || 0) + (v2 || 0)), 333 | validation: _.assign({}, result.validation, {group: _.assign({[key]: subValidationData.validation}, result.validation.group)}) 334 | } 335 | }, { 336 | summary, 337 | validation: _.assign({group: {}}, validation) 338 | }) 339 | } 340 | else { 341 | return { 342 | summary, 343 | validation 344 | } 345 | } 346 | }; 347 | 348 | const getField = (schema, fields)=> { 349 | if (typeof schema.type === 'string') return fields[schema.type]; 350 | else return schema.type; 351 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default} from './form'; -------------------------------------------------------------------------------- /test/entry.js: -------------------------------------------------------------------------------- 1 | require('bootstrap/dist/css/bootstrap.css'); 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | 6 | import * as buildOptions from 'rf-bootstrap3'; 7 | import Form from '../lib'; 8 | 9 | Form.defaultProps.buildOptions = buildOptions; 10 | 11 | const schema = { 12 | name: { 13 | type: 'Text', 14 | label: 'Name', 15 | validate(v) { 16 | if (!v) return 'Name is required.' 17 | } 18 | }, 19 | birthday: { 20 | type: 'Date', 21 | label: 'Birthday' 22 | }, 23 | sex: { 24 | type: 'RadioGroup', 25 | label: 'Sex', 26 | options: { 27 | items: { 28 | male: 'Male', 29 | female: 'Female', 30 | unknown: {label: 'Unknown', disabled: true} 31 | } 32 | }, 33 | validate(v) { 34 | if (!v) return 'Sex is required.' 35 | } 36 | }, 37 | friends: { 38 | label: 'Friends', 39 | array: { 40 | type: 'Text', 41 | label: 'Friend Name' 42 | }, 43 | validate(v) { 44 | if (v.length > 3) return 'You have too much friends.' 45 | } 46 | }, 47 | account: { 48 | label: 'Account', 49 | group: { 50 | username: { 51 | type: 'Text', 52 | label: 'Username', 53 | validate(v) {if (!v) return 'Username is required.'} 54 | }, 55 | password: { 56 | type: 'Password', 57 | label: 'Password', 58 | validate(v) {if (!v || v.length < 6) return 'Password length must be >= 6.'} 59 | }, 60 | confirmPassword: { 61 | type: 'Password', 62 | label: 'Confirm Password', 63 | validate(v, fv /*form value*/) { 64 | if (v !== fv.account.password) return 'Password does not match.' 65 | } 66 | } 67 | } 68 | } 69 | }; 70 | 71 | class TestPage extends React.Component { 72 | render() { 73 | return
console.log({value, summary, detail}) 76 | 77 | // you can specify value and onChange props to make the form work in controlled mode. 78 | // value: ... 79 | // onChange: (value, summary, detail)=> ... 80 | }}> 81 | 82 |
83 | } 84 | } 85 | 86 | let root = document.getElementById('root'); 87 | if (!root) { 88 | root = document.createElement('div'); 89 | root.id = 'root'; 90 | document.body.appendChild(root); 91 | } 92 | 93 | ReactDOM.render( 94 | , 95 | root 96 | ); 97 | 98 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Title 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: "./test/entry.js", 5 | output: { 6 | path: path.resolve(__dirname, 'local'), 7 | filename: "bundle.js" 8 | }, 9 | module: { 10 | preLoaders: [ 11 | { 12 | test: /\.js$/, 13 | loader: "source-map-loader" 14 | } 15 | ], 16 | loaders: [ 17 | {test: /\.css$/, loader: "style-loader!css-loader"}, 18 | {test: /.(png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000'}, 19 | { 20 | test: /\.jsx?$/, 21 | exclude: /(node_modules|bower_components|dist)/, 22 | loader: 'babel', // 'babel-loader' is also a legal name to reference 23 | query: { 24 | presets: ['es2015', 'react', 'stage-2'], 25 | cacheDirectory: true 26 | } 27 | } 28 | ] 29 | } 30 | }; --------------------------------------------------------------------------------