├── .babelrc
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── README.md
├── dist
└── form.js
├── package.json
└── src
└── form.jsx
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .babelrc
3 | README.md
4 | node_modules
5 | src
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.1
2 | deprecate this project
3 |
4 | ## 1.1.0
5 | support readOnly and disabled
6 |
7 | ## 1.0.1
8 |
9 | ## 1.0.0
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Shaking React Form
2 | build and use extensible react forms easily.
3 |
4 | ## Deprecated
5 | This project is deprecated. Please refer to the replacement project [rf-form](https://github.com/ShakingMap/rf-form).
6 |
7 | ## Activation
8 | Writing forms is boring, writing forms with validations is extremely boring. We want to build forms with validations
9 | easily, meanwhile, we want to use different styles. By searching and learning some existing react form projects, I found
10 | them difficult to use or hard to extend...
11 |
12 | ## Design Goals
13 | - based on react
14 | - schema driven
15 | - least dependencies
16 | - simple and robust
17 | - easy to use
18 | - easy to extend with different layouts, styles, input types
19 |
20 | ## Installation
21 | - install React
22 | - install core form by `npm install shaking-react-form --save`
23 | - write your form field class or install some existing field packages listed below.
24 |
25 | ## Available field packages
26 | - [raw-shaking-react-form-field](https://github.com/ShakingMap/raw-shaking-react-form-field)
27 | - [react-bootstrap-shaking-react-form-field](https://github.com/ShakingMap/react-bootstrap-shaking-react-form-field)
28 |
29 | ## Demo
30 | [More demos](https://github.com/ShakingMap/shaking-react-form-demo)
31 |
32 | Brief demo:
33 |
34 | import ShackingForm from 'shaking-react-form';
35 | import RawShakingReactFormField from 'raw-shaking-react-form-field';
36 |
37 | const schemas = {
38 | username: {
39 | label: 'Username',
40 | validate(v){
41 | if (!v) return 'username is required'
42 | },
43 | options: {
44 | placeholder: 'input username'
45 | }
46 | },
47 |
48 | password: {
49 | label: 'Password',
50 | type: 'password',
51 | validate(v){
52 | if (!v || v.length < 6) return 'password cannot be less than 6 letters'
53 | }
54 | },
55 | }
56 |
57 | class SomeReactComponent extends React.Component {
58 | // you can save form values as you like, here is just for convenience saving them directly into state
59 | constructor(props) {
60 | super(props);
61 | this.state = {};
62 | Object.keys(schemas).forEach(key=>this.state[key] = null);
63 | }
64 |
65 | render() {
66 | return
67 | {this.setState(values)}}
71 | onSubmit={(values)=>console.log('submit', values)}
72 | onErrors={(errors)=>console.log('errors', errors)}
73 | fieldClass={RawShakingReactFormField}
74 | >
75 |
76 |
77 |
78 | }
79 | }
80 |
81 | ## Apis
82 | ### props
83 | - {Array | Object} **schemas** - schemas for form fields. a schema is like
84 |
85 | {
86 | type: '...', // defined by the field class you use
87 | label: '...',
88 | validate: function(value){...}, // validation function for value of this field, return a string to indicate an validation error
89 | options: ..., // any thing a specific field needs, defined by the field class you use
90 | fieldClass: ..., // you can specify a the field class for this filed individually
91 | readOnly: bool,
92 | disabled: bool
93 | }
94 |
95 | - {ReactComponent} **[fieldClass]** - the field class the form will use to render the fields
96 | - {Array | Object} **[values]** - values for form fields. if not defined, the form will be uncontrolled
97 | - {Func} **[onChange]** - function(values), callback for change events of fields, only changed values are passed in
98 | - {Func} **[onSubmit]** - function(values), callback for submit event of the form, won't be triggered if there are validation errors
99 | - {Func} **[onErrors]** - function(errors), callback for submit event of the form, will be triggered only if there are validation errors
100 | - {Boolean} **[readOnly]**
101 | - {Boolean} **[disabled]**
102 |
103 | ### settings
104 | - **defaultFieldClass** - you can set a field class for all forms in your app by `ShackingForm.defaultFieldClass = ...`
105 |
106 | ## Advanced
107 | You can create your own field classes, it's not hard, and we hope you can contribute them to the society :)
108 |
109 | A field class is nothing but a react component which is responsible to **properly** render the field with several props:
110 |
111 | - {String} label
112 | - {String} type
113 | - {Any} options
114 | - {Any} value
115 | - {Func(value)} onChange
116 | - {String} validationState - null, 'success' or 'error'
117 | - {String} validationError
118 |
119 | there are no limit about type, options, and value, you can define and describe them clearly in your docs.
120 |
121 | it's helpful to have a look at [how we do](https://github.com/ShakingMap/raw-shaking-react-form-field/blob/master/src/field.jsx).
122 |
123 | ## Q&A
124 | ### How to customize style or layout of a field?
125 | You can create your own field class, use it for a individual field or compose it into default field class.
126 | see [how to create field class](https://github.com/ShakingMap/shaking-react-form#advanced) and [how to compose field classes](https://github.com/ShakingMap/shaking-react-form#how-to-compose-field-classes)
127 |
128 | ### How to compose field classes
129 | A field class can be just a swicher which renders other field class by type. So you can write such a swicher field class to compose many other field classes. [react-mapper](https://github.com/zhaoyao91/react-mapper) is a good helper for you to do this. see this [demo](https://github.com/ShakingMap/shaking-react-form-demo/tree/master/compose-field-class).
130 |
--------------------------------------------------------------------------------
/dist/form.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 |
7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
8 |
9 | var _react = require('react');
10 |
11 | var _react2 = _interopRequireDefault(_react);
12 |
13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14 |
15 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
16 |
17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
18 |
19 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
20 |
21 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
22 |
23 | var Form = function (_React$Component) {
24 | _inherits(Form, _React$Component);
25 |
26 | function Form() {
27 | _classCallCheck(this, Form);
28 |
29 | return _possibleConstructorReturn(this, Object.getPrototypeOf(Form).apply(this, arguments));
30 | }
31 |
32 | _createClass(Form, [{
33 | key: 'render',
34 | value: function render() {
35 | return _react2.default.createElement(
36 | 'form',
37 | { onSubmit: this.onFormSubmit.bind(this) },
38 | this.getFields(),
39 | this.props.children
40 | );
41 | }
42 | }, {
43 | key: 'onFormSubmit',
44 | value: function onFormSubmit(e) {
45 | var _this2 = this;
46 |
47 | e.preventDefault();
48 | var _props = this.props;
49 | var onSubmit = _props.onSubmit;
50 | var onErrors = _props.onErrors;
51 |
52 | var values = {};
53 | var errors = {};
54 | this.forEachKey(function (key) {
55 | var field = _this2.refs[key];
56 | field.enableValidation();
57 | values[key] = field.getValue();
58 | var error = field.getValidationError();
59 | if (error) errors[key] = error;
60 | });
61 | Object.keys(errors).length > 0 ? onErrors(errors) : onSubmit(values);
62 | }
63 | }, {
64 | key: 'forEachKey',
65 | value: function forEachKey(callback) {
66 | for (var key in this.props.schemas) {
67 | if (this.props.schemas.hasOwnProperty(key)) {
68 | callback(key);
69 | }
70 | }
71 | }
72 | }, {
73 | key: 'getFields',
74 | value: function getFields() {
75 | var _this3 = this;
76 |
77 | var _props2 = this.props;
78 | var schemas = _props2.schemas;
79 | var readOnly = _props2.readOnly;
80 | var disabled = _props2.disabled;
81 |
82 | return Object.keys(schemas).map(function (key) {
83 | var fieldClass = schemas[key].fieldClass || _this3.props.fieldClass || Form.defaultFieldClass;
84 | return _react2.default.createElement(Field, {
85 | ref: key,
86 | key: key,
87 | schema: schemas[key],
88 | fieldClass: fieldClass,
89 | value: _this3.props.values && _this3.props.values[key],
90 | onChange: function onChange(value) {
91 | return _this3.props.onChange(_defineProperty({}, key, value));
92 | },
93 | validate: schemas[key].validate || Form.defaultValidate,
94 | readOnly: readOnly,
95 | disabled: disabled
96 | });
97 | });
98 | }
99 | }]);
100 |
101 | return Form;
102 | }(_react2.default.Component);
103 |
104 | exports.default = Form;
105 |
106 |
107 | Form.propTypes = {
108 | schemas: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.object, _react2.default.PropTypes.array]).isRequired,
109 |
110 | fieldClass: _react2.default.PropTypes.any,
111 |
112 | values: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.object, _react2.default.PropTypes.array]),
113 |
114 | // func(values), called with only changed values
115 | onChange: _react2.default.PropTypes.func,
116 |
117 | // func(values)
118 | onSubmit: _react2.default.PropTypes.func,
119 |
120 | // func(errors)
121 | onErrors: _react2.default.PropTypes.func,
122 |
123 | readOnly: _react2.default.PropTypes.bool,
124 |
125 | disabled: _react2.default.PropTypes.bool
126 | };
127 |
128 | Form.defaultProps = {
129 | onChange: function onChange() {},
130 | onSubmit: function onSubmit() {},
131 | onValues: function onValues() {},
132 | onErrors: function onErrors() {}
133 | };
134 |
135 | Form.defaultFieldClass = null;
136 | Form.defaultValidate = function () {};
137 |
138 | var Field = function (_React$Component2) {
139 | _inherits(Field, _React$Component2);
140 |
141 | function Field(props) {
142 | _classCallCheck(this, Field);
143 |
144 | var _this4 = _possibleConstructorReturn(this, Object.getPrototypeOf(Field).call(this, props));
145 |
146 | var state = {
147 | enableValidation: false
148 | };
149 |
150 | // if there are no values, this is an uncontrolled form
151 | // but the fields are controlled by form
152 | if (props.value === undefined) state.value = null;
153 |
154 | var value = _this4.getValue(props, state);
155 | var result = _this4.validate(value);
156 | state.validationError = result.validationError;
157 | state.validationState = result.validationState;
158 |
159 | _this4.state = state;
160 | return _this4;
161 | }
162 |
163 | _createClass(Field, [{
164 | key: 'componentWillUpdate',
165 | value: function componentWillUpdate(nextProps, nextState) {
166 | var oldValue = this.getValue();
167 | var newValue = this.getValue(nextProps, nextState);
168 | if (oldValue !== newValue) {
169 | this.enableValidation();
170 | this.setState(this.validate(newValue));
171 | }
172 | }
173 | }, {
174 | key: 'render',
175 | value: function render() {
176 | var _this5 = this;
177 |
178 | var _props3 = this.props;
179 | var schema = _props3.schema;
180 | var onChange = _props3.onChange;
181 | var label = schema.label;
182 | var type = schema.type;
183 | var options = schema.options;
184 |
185 | var FieldClass = this.props.fieldClass;
186 | var validationState = this.state.enableValidation ? this.state.validationState : null;
187 | var validationError = this.state.enableValidation ? this.state.validationError : '';
188 | var onFieldChange = this.props.value === undefined ? function (value) {
189 | return _this5.setState({ value: value });
190 | } : onChange;
191 |
192 | var readOnly = this.props.readOnly || schema.readOnly;
193 | var disabled = this.props.disabled || schema.disabled;
194 |
195 | return _react2.default.createElement(FieldClass, {
196 | label: label,
197 | type: type,
198 | options: options,
199 | value: this.getValue(),
200 | onChange: onFieldChange,
201 | validationState: validationState,
202 | validationError: validationError,
203 | readOnly: readOnly,
204 | disabled: disabled
205 | });
206 | }
207 | }, {
208 | key: 'getValue',
209 | value: function getValue(props, state) {
210 | if (!props || !state) {
211 | props = this.props;
212 | state = this.state;
213 | }
214 | return props.value !== undefined ? props.value : state.value;
215 | }
216 | }, {
217 | key: 'getValidationError',
218 | value: function getValidationError() {
219 | return this.state.validationError;
220 | }
221 | }, {
222 | key: 'enableValidation',
223 | value: function enableValidation() {
224 | var flag = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
225 |
226 | this.setState({ enableValidation: flag });
227 | }
228 | }, {
229 | key: 'validate',
230 | value: function validate(value) {
231 | var validationError = this.props.validate(value) || '';
232 | var validationState = validationError ? 'error' : 'success';
233 | return { validationError: validationError, validationState: validationState };
234 | }
235 | }]);
236 |
237 | return Field;
238 | }(_react2.default.Component);
239 |
240 | Field.propTypes = {
241 | schema: _react2.default.PropTypes.object.isRequired,
242 | fieldClass: _react2.default.PropTypes.any.isRequired,
243 | value: _react2.default.PropTypes.any,
244 | onChange: _react2.default.PropTypes.func,
245 | validate: _react2.default.PropTypes.func,
246 | readOnly: _react2.default.PropTypes.bool,
247 | disabled: _react2.default.PropTypes.bool
248 | };
249 |
250 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shaking-react-form",
3 | "version": "1.1.1",
4 | "description": "build and use extensible react forms easily.",
5 | "main": "dist/form.js",
6 | "scripts": {
7 | "build": "node_modules/.bin/babel src/form.jsx > dist/form.js",
8 | "prepublish": "npm run build"
9 | },
10 | "author": "zhaoyao91",
11 | "license": "ISC",
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/ShakingMap/shaking-react-form.git"
15 | },
16 | "devDependencies": {
17 | "babel-cli": "^6.7.7",
18 | "babel-preset-es2015": "^6.6.0",
19 | "babel-preset-react": "^6.5.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Form extends React.Component {
4 | render() {
5 | return
9 | }
10 |
11 | onFormSubmit(e) {
12 | e.preventDefault();
13 | const {onSubmit, onErrors} = this.props;
14 | let values = {};
15 | let errors = {};
16 | this.forEachKey(key=> {
17 | const field = this.refs[key];
18 | field.enableValidation();
19 | values[key] = field.getValue();
20 | const error = field.getValidationError();
21 | if (error) errors[key] = error;
22 | });
23 | Object.keys(errors).length > 0 ? onErrors(errors) : onSubmit(values);
24 | }
25 |
26 | forEachKey(callback) {
27 | for (let key in this.props.schemas) {
28 | if (this.props.schemas.hasOwnProperty(key)) {
29 | callback(key);
30 | }
31 | }
32 | }
33 |
34 | getFields() {
35 | const {schemas, readOnly, disabled} = this.props;
36 | return Object.keys(schemas).map((key=> {
37 | const fieldClass = schemas[key].fieldClass || this.props.fieldClass || Form.defaultFieldClass;
38 | return this.props.onChange({[key]:value})}
45 | validate={schemas[key].validate || Form.defaultValidate}
46 | readOnly={readOnly}
47 | disabled={disabled}
48 | />
49 | }))
50 | }
51 | }
52 |
53 | Form.propTypes = {
54 | schemas: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.array]).isRequired,
55 |
56 | fieldClass: React.PropTypes.any,
57 |
58 | values: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.array]),
59 |
60 | // func(values), called with only changed values
61 | onChange: React.PropTypes.func,
62 |
63 | // func(values)
64 | onSubmit: React.PropTypes.func,
65 |
66 | // func(errors)
67 | onErrors: React.PropTypes.func,
68 |
69 | readOnly: React.PropTypes.bool,
70 |
71 | disabled: React.PropTypes.bool
72 | };
73 |
74 | Form.defaultProps = {
75 | onChange(){},
76 | onSubmit(){},
77 | onValues(){},
78 | onErrors(){}
79 | };
80 |
81 | Form.defaultFieldClass = null;
82 | Form.defaultValidate = function () {};
83 |
84 | class Field extends React.Component {
85 | constructor(props) {
86 | super(props);
87 |
88 | const state = {
89 | enableValidation: false
90 | };
91 |
92 | // if there are no values, this is an uncontrolled form
93 | // but the fields are controlled by form
94 | if (props.value === undefined) state.value = null;
95 |
96 | const value = this.getValue(props, state);
97 | const result = this.validate(value);
98 | state.validationError = result.validationError;
99 | state.validationState = result.validationState;
100 |
101 | this.state = state;
102 | }
103 |
104 | componentWillUpdate(nextProps, nextState) {
105 | const oldValue = this.getValue();
106 | const newValue = this.getValue(nextProps, nextState);
107 | if (oldValue !== newValue) {
108 | this.enableValidation();
109 | this.setState(this.validate(newValue))
110 | }
111 | }
112 |
113 | render() {
114 | const {schema, onChange} = this.props;
115 | const {label, type, options} = schema;
116 | const FieldClass = this.props.fieldClass;
117 | const validationState = this.state.enableValidation ? this.state.validationState : null;
118 | const validationError = this.state.enableValidation ? this.state.validationError : '';
119 | const onFieldChange = this.props.value === undefined ? (value)=>this.setState({value}) : onChange;
120 |
121 | const readOnly = this.props.readOnly || schema.readOnly;
122 | const disabled = this.props.disabled || schema.disabled;
123 |
124 | return
135 | }
136 |
137 | getValue(props, state) {
138 | if (!props || !state) {
139 | props = this.props;
140 | state = this.state;
141 | }
142 | return props.value !== undefined ? props.value : state.value;
143 | }
144 |
145 | getValidationError() {
146 | return this.state.validationError;
147 | }
148 |
149 | enableValidation(flag = true) {
150 | this.setState({enableValidation: flag});
151 | }
152 |
153 | validate(value) {
154 | const validationError = this.props.validate(value) || '';
155 | const validationState = validationError ? 'error' : 'success';
156 | return {validationError, validationState};
157 | }
158 | }
159 |
160 | Field.propTypes = {
161 | schema: React.PropTypes.object.isRequired,
162 | fieldClass: React.PropTypes.any.isRequired,
163 | value: React.PropTypes.any,
164 | onChange: React.PropTypes.func,
165 | validate: React.PropTypes.func,
166 | readOnly: React.PropTypes.bool,
167 | disabled: React.PropTypes.bool
168 | };
--------------------------------------------------------------------------------