Compare commits
309 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ebd84bb026 | |
|
|
7dc68fc633 | |
|
|
a20da5b7cd | |
|
|
c5dfa4b31a | |
|
|
f1ba9d417e | |
|
|
57f97cb045 | |
|
|
ac301f8950 | |
|
|
d68586180a | |
|
|
f395839e2c | |
|
|
73dd46b5e3 | |
|
|
84cdcb117c | |
|
|
c56e8c3328 | |
|
|
2b249113d8 | |
|
|
15493dce67 | |
|
|
89dbed0468 | |
|
|
168521e578 | |
|
|
42ba9616d2 | |
|
|
6b167cbfe1 | |
|
|
24a6b6217d | |
|
|
d6950885a1 | |
|
|
3cdcdf57ce | |
|
|
1800442ea1 | |
|
|
da8f041eca | |
|
|
9e923dd0dc | |
|
|
89a2d4287b | |
|
|
6767a6b9eb | |
|
|
d84397906b | |
|
|
112819f699 | |
|
|
4931256105 | |
|
|
7658d06cc8 | |
|
|
1df3e3520f | |
|
|
fec4576d1b | |
|
|
b39fd2ed74 | |
|
|
18467a94a1 | |
|
|
d5268a4eef | |
|
|
5b5bd0fb67 | |
|
|
46c1f2c250 | |
|
|
dcac495d79 | |
|
|
c4fa202ebf | |
|
|
7481b6da64 | |
|
|
fbd09119d3 | |
|
|
f67f8317c9 | |
|
|
59bde8324c | |
|
|
8636cdabc3 | |
|
|
cfebf17aea | |
|
|
6bcdb61b27 | |
|
|
300a53f172 | |
|
|
36b5dd1dab | |
|
|
8cf8409e3a | |
|
|
e4d35f999b | |
|
|
78b2ada909 | |
|
|
2a384f40a6 | |
|
|
b797979873 | |
|
|
13615e82e4 | |
|
|
2639319f9b | |
|
|
2be44f1b32 | |
|
|
97923876d9 | |
|
|
9fb4b93fec | |
|
|
dd15a840a0 | |
|
|
6f2a21e3ef | |
|
|
5bfa7e53f1 | |
|
|
4ca7a57af7 | |
|
|
3bd3ad699e | |
|
|
19e28c6437 | |
|
|
2846f0fadd | |
|
|
8254580e62 | |
|
|
4670a9cc1f | |
|
|
2c6f7fe8f0 | |
|
|
404f696bfb | |
|
|
e72f34a3d1 | |
|
|
0c5e927f8b | |
|
|
43a8f66150 | |
|
|
2c4f73dd8e | |
|
|
5083c31c8d | |
|
|
f559c8f9d4 | |
|
|
bf77058a87 | |
|
|
32aac21a4e | |
|
|
349d5c7922 | |
|
|
523ab69a4e | |
|
|
a8dd273148 | |
|
|
3791b1976f | |
|
|
a96f5246e7 | |
|
|
8fe0d4d73f | |
|
|
5deb7554c9 | |
|
|
5c350d73f5 | |
|
|
6a0d23cc03 | |
|
|
d25a0ace2c | |
|
|
e19a2eadbd | |
|
|
5c33b56e71 | |
|
|
7ab78f9603 | |
|
|
cfb3a5af6f | |
|
|
13688f1716 | |
|
|
7f6ab4f52c | |
|
|
6b521d35e7 | |
|
|
c311e144fb | |
|
|
b1275d813e | |
|
|
882eaa1801 | |
|
|
0acaf5e14c | |
|
|
034a5725db | |
|
|
38546bc76b | |
|
|
2a1be46ffe | |
|
|
575321d2e2 | |
|
|
3dcfb90044 | |
|
|
87f14ae418 | |
|
|
712f38883b | |
|
|
3258545a55 | |
|
|
c0fa87f5cb | |
|
|
d623d9ddf1 | |
|
|
338ceb853a | |
|
|
c04623ae05 | |
|
|
dcac01159a | |
|
|
e38154f73a | |
|
|
344154b377 | |
|
|
dda96c46f6 | |
|
|
619a85657f | |
|
|
82edabde15 | |
|
|
977bf7caaf | |
|
|
4da58c12d7 | |
|
|
c84d5e2a45 | |
|
|
31e9590172 | |
|
|
8691866c0d | |
|
|
357f4adf71 | |
|
|
89759fefde | |
|
|
b86cca70b3 | |
|
|
9e5723c554 | |
|
|
818b1defbf | |
|
|
7e3b4b5e59 | |
|
|
701e2308a4 | |
|
|
4279dd59a2 | |
|
|
1c74cb377f | |
|
|
958bd98223 | |
|
|
07f2a51a30 | |
|
|
114db75de9 | |
|
|
8af9abec36 | |
|
|
9db2b9fccf | |
|
|
c37c053a66 | |
|
|
c394ce0925 | |
|
|
0395f61233 | |
|
|
d7e7dfc55e | |
|
|
4fcb74f8d8 | |
|
|
a1cf2236df | |
|
|
dbb0653a2b | |
|
|
23f91c4373 | |
|
|
99500432dc | |
|
|
2ef73a2ec2 | |
|
|
cca4973382 | |
|
|
d7629a3f6a | |
|
|
5b2ca883f3 | |
|
|
538003698c | |
|
|
c9078b69c2 | |
|
|
49d7d39005 | |
|
|
e5d27b832c | |
|
|
5ae260820e | |
|
|
effa9de53f | |
|
|
d225fa3d2b | |
|
|
9ab0acff82 | |
|
|
7ef6c51f1f | |
|
|
f168b53a10 | |
|
|
37018b10fa | |
|
|
f568ac5970 | |
|
|
9a7bff0e03 | |
|
|
750fe1508f | |
|
|
a7ac20ba41 | |
|
|
9b2d9598e8 | |
|
|
accc0815db | |
|
|
8acaeadaee | |
|
|
58e60aba63 | |
|
|
cf1550d4f2 | |
|
|
735325a377 | |
|
|
6b696d8f03 | |
|
|
00cef86cd7 | |
|
|
d90a355436 | |
|
|
2809ea43a4 | |
|
|
8970a6756b | |
|
|
ce9f566a38 | |
|
|
a8231424e9 | |
|
|
ccdb04c7d9 | |
|
|
b0467856c5 | |
|
|
dd52595dd9 | |
|
|
6d64dd34b0 | |
|
|
355c0bbee3 | |
|
|
b18b4e8c79 | |
|
|
f64d2443b9 | |
|
|
33ae737868 | |
|
|
d8cd404412 | |
|
|
cd37020a04 | |
|
|
8f4e2566d5 | |
|
|
e4b5dbf8f9 | |
|
|
1a70c89996 | |
|
|
4267c40f3b | |
|
|
7e909fb188 | |
|
|
9eb7d5087b | |
|
|
2364e4ffe2 | |
|
|
1a450d81f2 | |
|
|
6c3d9a0315 | |
|
|
aaac6d0618 | |
|
|
7227895ee9 | |
|
|
4736beecb8 | |
|
|
1475dbb88b | |
|
|
31a78c74de | |
|
|
9fdca026bf | |
|
|
d0af1375a2 | |
|
|
e8cd6a8ce8 | |
|
|
1fb4461121 | |
|
|
261d605c19 | |
|
|
2ad4cdd765 | |
|
|
01e6b36a36 | |
|
|
54a857638a | |
|
|
6eb83c0c8a | |
|
|
785ac0ca54 | |
|
|
1e9ff5e527 | |
|
|
a0bab16ca3 | |
|
|
617cc21ee5 | |
|
|
4d63e3b0b2 | |
|
|
8b42fd254a | |
|
|
c5bbc06178 | |
|
|
4cd34a0084 | |
|
|
b0738a5032 | |
|
|
36d4439019 | |
|
|
5e00ea281f | |
|
|
fe8ec06af9 | |
|
|
82457b66f6 | |
|
|
53a4985a7e | |
|
|
4a40cd21f1 | |
|
|
a33a9db353 | |
|
|
ad83a10894 | |
|
|
f1d9d6c15c | |
|
|
0964b07e1d | |
|
|
a348b1aa20 | |
|
|
8cbd6e19ff | |
|
|
ebd17fde62 | |
|
|
eb3eaa6544 | |
|
|
4814fdbe79 | |
|
|
a90b98111f | |
|
|
5116f5999d | |
|
|
47dd80b67f | |
|
|
dca9f78524 | |
|
|
703e879f75 | |
|
|
716498e6d7 | |
|
|
c49f7949f2 | |
|
|
6e74d92392 | |
|
|
5e12c95d36 | |
|
|
0d34e7b5bb | |
|
|
36d9741535 | |
|
|
dd259c5b13 | |
|
|
879b19f2cd | |
|
|
6fcc9391c7 | |
|
|
891526a0fd | |
|
|
27f705a3d0 | |
|
|
011d98dfad | |
|
|
bade05b9cd | |
|
|
2c64535522 | |
|
|
ba71d27135 | |
|
|
51bdf8da4f | |
|
|
86aec656df | |
|
|
d9bf45d417 | |
|
|
6b69c0d8d5 | |
|
|
d04bae94f9 | |
|
|
5323d08802 | |
|
|
cef4495d41 | |
|
|
a9c4746c21 | |
|
|
7fb17a752d | |
|
|
5cc979e249 | |
|
|
fa4caa6588 | |
|
|
9ef8590f80 | |
|
|
72a7be5d64 | |
|
|
cf93061296 | |
|
|
c15b0a200a | |
|
|
a65b2943b0 | |
|
|
a72838b446 | |
|
|
385263c383 | |
|
|
bbc6ad38e2 | |
|
|
084aa0772a | |
|
|
03bc5a4079 | |
|
|
e9310002d1 | |
|
|
86ba085cc4 | |
|
|
0834810d67 | |
|
|
f0faede640 | |
|
|
15b6df9526 | |
|
|
a008b1ce8d | |
|
|
418c833e2d | |
|
|
1ec5868f64 | |
|
|
23bad21119 | |
|
|
196bba3fda | |
|
|
80227cc50f | |
|
|
05e3688c8b | |
|
|
7c95bf191a | |
|
|
4127480839 | |
|
|
7163b30246 | |
|
|
94e69e7dc0 | |
|
|
24b9110a62 | |
|
|
b8c4414976 | |
|
|
9e430bbf83 | |
|
|
e9743a2df1 | |
|
|
7830defb34 | |
|
|
d813f2ade8 | |
|
|
915ddb8cee | |
|
|
63938025fa | |
|
|
b04c989cf6 | |
|
|
dc6ac20285 | |
|
|
32f67e24ac | |
|
|
4b960bf017 | |
|
|
0b0e0a1b49 | |
|
|
2289290998 | |
|
|
51bce0315b | |
|
|
fb47da210f | |
|
|
df4e84a10a | |
|
|
d12ae507d0 | |
|
|
20f1d71f96 |
|
|
@ -0,0 +1,12 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -1,28 +1,2 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# Commenting this out is preferred by some people, see
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Users Environment Variables
|
||||
.lock-wscript
|
||||
lib
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.babelrc
|
||||
.editorconfig
|
||||
.travis.yml
|
||||
testrunner.js
|
||||
webpack.production.config.js
|
||||
examples/
|
||||
release/
|
||||
tests/
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '4.1'
|
||||
|
|
@ -0,0 +1,776 @@
|
|||
# API
|
||||
|
||||
- [Formsy.Form](#formsyform)
|
||||
- [className](#classname)
|
||||
- [mapping](#mapping)
|
||||
- [validationErrors](#validationerrors)
|
||||
- [onSubmit()](#onsubmit)
|
||||
- [onValid()](#onvalid)
|
||||
- [onInvalid()](#oninvalid)
|
||||
- [onValidSubmit()](#onvalidsubmit)
|
||||
- [onInvalidSubmit()](#oninvalidsubmit)
|
||||
- [onChange()](#onchange)
|
||||
- [reset()](#resetform)
|
||||
- [getModel()](#getmodel)
|
||||
- [updateInputsWithError()](#updateinputswitherrorerrors)
|
||||
- [preventExternalInvalidation](#preventexternalinvalidation)
|
||||
- [Formsy.Mixin](#formsymixin)
|
||||
- [name](#name)
|
||||
- [value](#value)
|
||||
- [validations](#validations)
|
||||
- [validationError](#validationerror)
|
||||
- [validationErrors](#validationerrors-1)
|
||||
- [required](#required)
|
||||
- [getValue()](#getvalue)
|
||||
- [setValue()](#setvalue)
|
||||
- [resetValue()](#resetvalue)
|
||||
- [getErrorMessage()](#geterrormessage)
|
||||
- [getErrorMessages()](#geterrormessages)
|
||||
- [isValid()](#isvalid)
|
||||
- [isValidValue()](#isvalidvalue)
|
||||
- [isRequired()](#isrequired)
|
||||
- [showRequired()](#showrequired)
|
||||
- [showError()](#showerror)
|
||||
- [isPristine()](#ispristine)
|
||||
- [isFormDisabled()](#isformdisabled)
|
||||
- [isFormSubmitted()](#isformsubmitted)
|
||||
- [validate](#validate)
|
||||
- [formNoValidate](#formnovalidate)
|
||||
- [Formsy.HOC](#formsyhoc)
|
||||
- [innerRef](#innerRef)
|
||||
- [Formsy.Decorator](#formsydecorator)
|
||||
- [Formsy.addValidationRule](#formsyaddvalidationrule)
|
||||
- [Validators](#validators)
|
||||
|
||||
### <a name="formsyform">Formsy.Form</a>
|
||||
|
||||
#### <a name="classname">className</a>
|
||||
```jsx
|
||||
<Formsy.Form className="my-class"></Formsy.Form>
|
||||
```
|
||||
Sets a class name on the form itself.
|
||||
|
||||
#### <a name="mapping">mapping</a>
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
mapInputs: function (inputs) {
|
||||
return {
|
||||
'field1': inputs.foo,
|
||||
'field2': inputs.bar
|
||||
};
|
||||
},
|
||||
submit: function (model) {
|
||||
model; // {field1: '', field2: ''}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.submit} mapping={this.mapInputs}>
|
||||
<MyInput name="foo" value=""/>
|
||||
<MyInput name="bar" value=""/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
})
|
||||
```
|
||||
Use mapping to change the data structure of your input elements. This structure is passed to the submit hooks.
|
||||
|
||||
#### <a name="validationerrors">validationErrors</a>
|
||||
You can manually pass down errors to your form. In combination with `onChange` you are able to validate using an external validator.
|
||||
|
||||
```jsx
|
||||
var Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
validateForm: function (values) {
|
||||
if (!values.foo) {
|
||||
this.setState({
|
||||
validationErrors: {
|
||||
foo: 'Has no value'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
validationErrors: {}
|
||||
});
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onChange={this.validateForm} validationErrors={this.state.validationErrors}>
|
||||
<MyFormElement name="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### <a name="onsubmit">onSubmit(data, resetForm, invalidateForm)</a>
|
||||
```jsx
|
||||
<Formsy.Form onSubmit={this.showFormLoader}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when the submit button has been clicked.
|
||||
|
||||
The first argument is the data of the form. The second argument will reset the form. The third argument will invalidate the form by taking an object that maps to inputs. This is useful for server side validation. E.g. `{email: "This email is taken"}`. Resetting or invalidating the form will cause **setState** to run on the form element component.
|
||||
|
||||
#### <a name="onvalid">onValid()</a>
|
||||
```jsx
|
||||
<Formsy.Form onValid={this.enableSubmitButton}></Formsy.Form>
|
||||
```
|
||||
Whenever the form becomes valid the "onValid" handler is called. Use it to change state of buttons or whatever your heart desires.
|
||||
|
||||
#### <a name="oninvalid">onInvalid()</a>
|
||||
```jsx
|
||||
<Formsy.Form onInvalid={this.disableSubmitButton}></Formsy.Form>
|
||||
```
|
||||
Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state.
|
||||
|
||||
#### <a name="onvalidsubmit">onValidSubmit(model, resetForm, invalidateForm)</a>
|
||||
```jsx
|
||||
<Formsy.Form onValidSubmit={this.sendToServer}></Formsy.Form>
|
||||
```
|
||||
Triggers when form is submitted with a valid state. The arguments are the same as on `onSubmit`.
|
||||
|
||||
#### <a name="oninvalidsubmit">onInvalidSubmit(model, resetForm, invalidateForm)</a>
|
||||
```jsx
|
||||
<Formsy.Form onInvalidSubmit={this.notifyFormError}></Formsy.Form>
|
||||
```
|
||||
Triggers when form is submitted with an invalid state. The arguments are the same as on `onSubmit`.
|
||||
|
||||
#### <a name="onchange">onChange(currentValues, isChanged)</a>
|
||||
```jsx
|
||||
<Formsy.Form onChange={this.saveCurrentValuesToLocalStorage}></Formsy.Form>
|
||||
```
|
||||
"onChange" triggers when setValue is called on your form elements. It is also triggered when dynamic form elements have been added to the form. The "currentValues" is an object where the key is the name of the input and the value is the current value. The second argument states if the forms initial values actually has changed.
|
||||
|
||||
#### <a name="resetform">reset(values)</a>
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
resetForm: function () {
|
||||
this.refs.form.reset();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form ref="form">
|
||||
...
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Manually reset the form to its pristine state. You can also pass an object that inserts new values into the inputs. Keys are name of input and value is of course the value.
|
||||
|
||||
#### <a name="getmodel">getModel()</a>
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
getMyData: function () {
|
||||
alert(this.refs.form.getModel());
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form ref="form">
|
||||
...
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Manually get values from all registered components. Keys are name of input and value is of course the value.
|
||||
|
||||
#### <a name="updateInputsWithError">updateInputsWithError(errors)</a>
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
someFunction: function () {
|
||||
this.refs.form.updateInputsWithError({
|
||||
email: 'This email is taken',
|
||||
'field[10]': 'Some error!'
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form ref="form">
|
||||
...
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Manually invalidate the form by taking an object that maps to inputs. This is useful for server side validation. You can also use a third parameter to the [`onSubmit`](#onsubmitdata-resetform-invalidateform), [`onValidSubmit`](#onvalidsubmitmodel-resetform-invalidateform) or [`onInvalidSubmit`](#oninvalidsubmitmodel-resetform-invalidateform).
|
||||
|
||||
#### <a name="preventExternalInvalidation">preventExternalInvalidation</a>
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
onSubmit: function (model, reset, invalidate) {
|
||||
invalidate({
|
||||
foo: 'Got some error'
|
||||
});
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit} preventExternalInvalidation>
|
||||
...
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
With the `preventExternalInvalidation` the input will not be invalidated though it has an error.
|
||||
|
||||
### <a name="formsymixin">Formsy.Mixin</a>
|
||||
|
||||
#### <a name="name">name</a>
|
||||
```jsx
|
||||
<MyInputComponent name="email"/>
|
||||
<MyInputComponent name="address.street"/>
|
||||
```
|
||||
The name is required to register the form input component in the form. You can also use dot notation. This will result in the "form model" being a nested object. `{email: 'value', address: {street: 'value'}}`.
|
||||
|
||||
#### <a name="value">value</a>
|
||||
```jsx
|
||||
<MyInputComponent name="email" value="My initial value"/>
|
||||
```
|
||||
You should always use the [**getValue()**](#getvalue) method inside your formsy form element. To pass an initial value, use the value attribute. This value will become the "pristine" value and any reset of the form will bring back this value.
|
||||
|
||||
#### <a name="validations">validations</a>
|
||||
```jsx
|
||||
<MyInputComponent name="email" validations="isEmail"/>
|
||||
<MyInputComponent name="number" validations="isNumeric,isLength:5"/>
|
||||
<MyInputComponent name="number" validations={{
|
||||
isNumeric: true,
|
||||
isLength: 5
|
||||
}}/>
|
||||
<MyInputComponent name="number" validations={{
|
||||
myCustomIsFiveValidation: function (values, value) {
|
||||
values; // Other current values in form {foo: 'bar', 'number': 5}
|
||||
value; // 5
|
||||
return 5 === value ? true : 'No five'; // You can return an error
|
||||
}
|
||||
}}/>
|
||||
```
|
||||
A comma separated list with validation rules. Take a look at [**Validators**](#validators) to see default rules. Use ":" to separate argument passed to the validator. The argument will go through a **JSON.parse** converting them into correct JavaScript types. Meaning:
|
||||
|
||||
```jsx
|
||||
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange']"/>
|
||||
<MyInputComponent name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/>
|
||||
```
|
||||
Works just fine.
|
||||
|
||||
#### <a name="validationerror">validationError</a>
|
||||
```jsx
|
||||
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email"/>
|
||||
```
|
||||
The message that will show when the form input component is invalid. It will be used as a default error.
|
||||
|
||||
#### <a name="validationerrors">validationErrors</a>
|
||||
```jsx
|
||||
<MyInputComponent
|
||||
name="email"
|
||||
validations={{
|
||||
isEmail: true,
|
||||
maxLength: 50
|
||||
}}
|
||||
validationErrors={{
|
||||
isEmail: 'You have to type valid email',
|
||||
maxLength: 'You can not type in more than 50 characters'
|
||||
}}
|
||||
/>
|
||||
```
|
||||
The message that will show when the form input component is invalid. You can combine this with `validationError`. Keys not found in `validationErrors` defaults to the general error message.
|
||||
|
||||
#### <a name="required">required</a>
|
||||
```jsx
|
||||
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email" required/>
|
||||
```
|
||||
|
||||
A property that tells the form that the form input component value is required. By default it uses `isDefaultRequiredValue`, but you can define your own definition of what defined a required state.
|
||||
|
||||
```jsx
|
||||
<MyInputComponent name="email" required="isFalse"/>
|
||||
```
|
||||
Would be typical for a checkbox type of form element that must be checked, e.g. agreeing to Terms of Service.
|
||||
|
||||
#### <a name="getvalue">getValue()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return (
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Gets the current value of the form input component.
|
||||
|
||||
#### <a name="setvalue">setValue(value)</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc. Running this method will trigger a **setState()** on the component and do a render.
|
||||
|
||||
#### <a name="resetvalue">resetValue()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<button onClick={this.resetValue()}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Resets to empty value. This will run a **setState()** on the component and do a render.
|
||||
|
||||
#### <a name="geterrormessage">getErrorMessage()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Will return the validation message set if the form input component is invalid. If form input component is valid it returns **null**.
|
||||
|
||||
#### <a name="geterrormessages">getErrorMessages()</a>
|
||||
Will return the validation messages set if the form input component is invalid. If form input component is valid it returns empty array.
|
||||
|
||||
#### <a name="isvalid">isValid()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var face = this.isValid() ? ':-)' : ':-(';
|
||||
return (
|
||||
<div>
|
||||
<span>{face}</span>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Returns the valid state of the form input component.
|
||||
|
||||
#### <a name="isvalidvalue">isValidValue()</a>
|
||||
You can pre-verify a value against the passed validators to the form element.
|
||||
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
if (this.isValidValue(event.target.value)) {
|
||||
this.setValue(event.target.value);
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return <input type="text" onChange={this.changeValue} value={this.getValue()}/>;
|
||||
}
|
||||
});
|
||||
|
||||
var MyForm = React.createClass({
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<MyInput name="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### <a name="isrequired">isRequired()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<span>{this.props.label} {this.isRequired() ? '*' : null}</span>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Returns true if the required property has been passed.
|
||||
|
||||
#### <a name="showrequired">showRequired()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var className = this.showRequired() ? 'required' : '';
|
||||
return (
|
||||
<div className={className}>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Lets you check if the form input component should indicate if it is a required field. This happens when the form input component value is empty and the required prop has been passed.
|
||||
|
||||
#### <a name="showerror">showError()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var className = this.showRequired() ? 'required' : this.showError() ? 'error' : '';
|
||||
return (
|
||||
<div className={className}>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Lets you check if the form input component should indicate if there is an error. This happens if there is a form input component value and it is invalid or if a server error is received.
|
||||
|
||||
#### <a name="ispristine">isPristine()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.isPristine() ? 'You have not touched this yet' : ''}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
By default all formsy input elements are pristine, which means they are not "touched". As soon as the [**setValue**](#setvaluevalue) method is run it will no longer be pristine.
|
||||
|
||||
**note!** When the form is reset, using the resetForm callback function on for example [**onSubmit**](#onsubmitdata-resetform-invalidateform) the inputs are reset to their pristine state.
|
||||
|
||||
#### <a name="isformdisabled">isFormDisabled()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" value={this.getValue()} disabled={this.isFormDisabled()}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<Formy.Form disabled={true}/>);
|
||||
```
|
||||
You can now disable the form itself with a prop and use **isFormDisabled()** inside form elements to verify this prop.
|
||||
|
||||
#### <a name="isformsubmitted">isFormSubmitted()</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
var error = this.isFormSubmitted() ? this.getErrorMessage() : null;
|
||||
return (
|
||||
<div>
|
||||
<input type="text" value={this.getValue()}/>
|
||||
{error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
You can check if the form has been submitted.
|
||||
|
||||
#### <a name="validate">validate</a>
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.target.value);
|
||||
},
|
||||
validate: function () {
|
||||
return !!this.getValue();
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" value={this.getValue()} onChange={this.changeValue}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
React.render(<Formy.Form disabled={true}/>);
|
||||
```
|
||||
You can create custom validation inside a form element. The validate method defined will be run when you set new values to the form element. It will also be run when the form validates itself. This is an alternative to passing in validation rules as props.
|
||||
|
||||
#### <a name="formnovalidate">formNoValidate</a>
|
||||
To avoid native validation behavior on inputs, use the React `formNoValidate` property.
|
||||
```jsx
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input formNoValidate type="number"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### <a name="formsyhoc">Formsy.HOC</a>
|
||||
The same methods as the mixin are exposed to the HOC version of the element component, though through the `props`, not on the instance.
|
||||
```jsx
|
||||
import {HOC} from 'formsy-react';
|
||||
|
||||
class MyInputHoc extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<input value={this.props.getValue()} onChange={(e) => this.props.setValue(e.target.value)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default HOC(MyInputHoc);
|
||||
```
|
||||
|
||||
#### <a name="innerRef">innerRef</a>
|
||||
|
||||
Use an `innerRef` prop to get a reference to your DOM node.
|
||||
|
||||
```jsx
|
||||
var MyForm = React.createClass({
|
||||
componentDidMount() {
|
||||
this.searchInput.focus()
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<MyInputHoc name="search" innerRef={(c) => { this.searchInput = c; }} />
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### <a name="formsydecorator">Formsy.Decorator</a>
|
||||
The same methods as the mixin are exposed to the decorator version of the element component, though through the `props`, not on the instance.
|
||||
```jsx
|
||||
import {Decorator as FormsyElement} from 'formsy-react';
|
||||
|
||||
@FormsyElement()
|
||||
class MyInput extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<input value={this.props.getValue()} onChange={(e) => this.props.setValue(e.target.value)}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default MyInput
|
||||
```
|
||||
|
||||
### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a>
|
||||
An example:
|
||||
```jsx
|
||||
Formsy.addValidationRule('isFruit', function (values, value) {
|
||||
return ['apple', 'orange', 'pear'].indexOf(value) >= 0;
|
||||
});
|
||||
```
|
||||
```jsx
|
||||
<MyInputComponent name="fruit" validations="isFruit"/>
|
||||
```
|
||||
Another example:
|
||||
```jsx
|
||||
Formsy.addValidationRule('isIn', function (values, value, array) {
|
||||
return array.indexOf(value) >= 0;
|
||||
});
|
||||
```
|
||||
```jsx
|
||||
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange', 'pear']"/>
|
||||
```
|
||||
Cross input validation:
|
||||
```jsx
|
||||
Formsy.addValidationRule('isMoreThan', function (values, value, otherField) {
|
||||
// The this context points to an object containing the values
|
||||
// {childAge: "", parentAge: "5"}
|
||||
// otherField argument is from the validations rule ("childAge")
|
||||
return Number(value) > Number(values[otherField]);
|
||||
});
|
||||
```
|
||||
```jsx
|
||||
<MyInputComponent name="childAge"/>
|
||||
<MyInputComponent name="parentAge" validations="isMoreThan:childAge"/>
|
||||
```
|
||||
## <a name="validators">Validators</a>
|
||||
**matchRegexp**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations={{
|
||||
matchRegexp: /foo/
|
||||
}}/>
|
||||
```
|
||||
Returns true if the value is thruthful
|
||||
|
||||
_For more complicated regular expressions (emoji, international characters) you can use [xregexp](https://github.com/slevithan/xregexp). See [this comment](https://github.com/christianalfoni/formsy-react/issues/407#issuecomment-266306783) for an example._
|
||||
|
||||
**isEmail**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isEmail"/>
|
||||
```
|
||||
Return true if it is an email
|
||||
|
||||
**isUrl**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isUrl"/>
|
||||
```
|
||||
Return true if it is an url
|
||||
|
||||
**isExisty**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isExisty"/>
|
||||
```
|
||||
Returns true if the value is not undefined or null
|
||||
|
||||
**isUndefined**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isUndefined"/>
|
||||
```
|
||||
Returns true if the value is the undefined
|
||||
|
||||
**isEmptyString**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isEmptyString"/>
|
||||
```
|
||||
Returns true if the value is an empty string
|
||||
|
||||
**isTrue**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isTrue"/>
|
||||
```
|
||||
Returns true if the value is the boolean true
|
||||
|
||||
**isFalse**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isFalse"/>
|
||||
```
|
||||
Returns true if the value is the boolean false
|
||||
|
||||
**isAlpha**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isAlpha"/>
|
||||
```
|
||||
Returns true if string is only letters
|
||||
|
||||
**isNumeric**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isNumeric"/>
|
||||
```
|
||||
Returns true if string only contains numbers. Examples: 42; -3.14
|
||||
|
||||
**isAlphanumeric**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isAlphanumeric"/>
|
||||
```
|
||||
Returns true if string only contains letters or numbers
|
||||
|
||||
**isInt**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isInt"/>
|
||||
```
|
||||
Returns true if string represents integer value. Examples: 42; -12; 0
|
||||
|
||||
**isFloat**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isFloat"/>
|
||||
```
|
||||
Returns true if string represents float value. Examples: 42; -3.14; 1e3
|
||||
|
||||
**isWords**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isWords"/>
|
||||
```
|
||||
Returns true if string is only letters, including spaces and tabs
|
||||
|
||||
**isSpecialWords**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isSpecialWords"/>
|
||||
```
|
||||
Returns true if string is only letters, including special letters (a-z,ú,ø,æ,å)
|
||||
|
||||
**equals:value**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="equals:4"/>
|
||||
```
|
||||
Return true if the value from input component matches value passed (==).
|
||||
|
||||
**equalsField:fieldName**
|
||||
```jsx
|
||||
<MyInputComponent name="password"/>
|
||||
<MyInputComponent name="repeated_password" validations="equalsField:password"/>
|
||||
```
|
||||
Return true if the value from input component matches value passed (==).
|
||||
|
||||
**isLength:length**
|
||||
```jsx
|
||||
<MyInputComponent name="foo" validations="isLength:8"/>
|
||||
```
|
||||
Returns true if the value length is the equal.
|
||||
|
||||
**minLength:length**
|
||||
```jsx
|
||||
<MyInputComponent name="number" validations="minLength:1"/>
|
||||
```
|
||||
Return true if the value is more or equal to argument.
|
||||
**Also returns true for an empty value.** If you want to get false, then you should use [`required`](#required) additionally.
|
||||
|
||||
**maxLength:length**
|
||||
```jsx
|
||||
<MyInputComponent name="number" validations="maxLength:5"/>
|
||||
```
|
||||
Return true if the value is less or equal to argument
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
This is the old CHANGES file. Please look at [releases](https://github.com/christianalfoni/formsy-react/releases) for latest changes.
|
||||
|
||||
**0.8.0**
|
||||
- Fixed bug where dynamic form elements gave "not mounted" error (Thanks @sdemjanenko)
|
||||
- React is now a peer dependency (Thanks @snario)
|
||||
- Dynamically updated values should now work with initial "undefined" value (Thanks @sdemjanenko)
|
||||
- Validations are now dynamic. Change the prop and existing values are re-validated (thanks @bryannaegele)
|
||||
- You can now set a "disabled" prop on the form and check "isFormDisabled()" in form elements
|
||||
- Refactored some code and written a couple of tests
|
||||
|
||||
**0.7.2**:
|
||||
- isNumber validation now supports float (Thanks @hahahana)
|
||||
- Form XHR calls now includes CSRF headers, if exists (Thanks @hahahana)
|
||||
|
||||
**0.7.1**
|
||||
- Fixed bug where external update of value on pristine form element did not update the form model (Thanks @sdemjanenko)
|
||||
- Fixed bug where children are null/undefined (Thanks @sdemjanenko)
|
||||
|
||||
**0.7.0**
|
||||
- Dynamic form elements. Add them at any point and they will be registered with the form
|
||||
- **onChange()** handler is called whenever an form element has changed its value or a new form element is added to the form
|
||||
- isNumeric validator now also handles actual numbers, not only strings
|
||||
- Some more tests
|
||||
|
||||
**0.6.0**
|
||||
- **onSubmit()** now has the same signature regardless of passing url attribute or not
|
||||
- **isPristine()** is a new method to handle "touched" form elements (thanks @FoxxMD)
|
||||
- Mapping attributes to pass a function that maps input values to new structure. The new structure is either passed to *onSubmit* and/or to the server when using a url attribute (thanks for feedback @MattAitchison)
|
||||
- Added default "equalsField" validation rule
|
||||
- Lots of tests!
|
||||
|
||||
**0.5.2**
|
||||
- Fixed bug with handlers in ajax requests (Thanks @smokku)
|
||||
|
||||
**0.5.1**
|
||||
- Fixed bug with empty validations
|
||||
|
||||
**0.5.0**
|
||||
- Added [cross input validation](#formsyaddvalidationrule)
|
||||
- Fixed bug where validation rule refers to a string
|
||||
- Added "invalidateForm" function when manually submitting the form
|
||||
|
||||
**0.4.1**
|
||||
- Fixed bug where form element is required, but no validations
|
||||
|
||||
**0.4.0**:
|
||||
- Possibility to handle form data manually using "onSubmit"
|
||||
- Added two more default rules. *isWords* and *isSpecialWords*
|
||||
|
||||
**0.3.0**:
|
||||
- Deprecated everything related to buttons automatically added
|
||||
- Added onValid and onInvalid handlers, use those to manipulate submit buttons etc.
|
||||
|
||||
**0.2.3**:
|
||||
|
||||
- Fixed bug where child does not have props property
|
||||
|
||||
**0.2.2**:
|
||||
|
||||
- Fixed bug with updating the props
|
||||
|
||||
**0.2.1**:
|
||||
|
||||
- Cancel button displays if onCancel handler is defined
|
||||
|
||||
**0.2.0**:
|
||||
|
||||
- Implemented hasValue() method
|
||||
|
||||
**0.1.3**:
|
||||
|
||||
- Fixed resetValue bug
|
||||
|
||||
**0.1.2**:
|
||||
|
||||
- Fixed isValue check to empty string, needs to support false and 0
|
||||
|
||||
**0.1.1**:
|
||||
|
||||
- Added resetValue method
|
||||
- Changed value check of showRequired
|
||||
94
Gulpfile.js
94
Gulpfile.js
|
|
@ -1,94 +0,0 @@
|
|||
var gulp = require('gulp');
|
||||
var browserify = require('browserify');
|
||||
var watchify = require('watchify');
|
||||
var source = require('vinyl-source-stream');
|
||||
var gulpif = require('gulp-if');
|
||||
var uglify = require('gulp-uglify');
|
||||
var streamify = require('gulp-streamify');
|
||||
var notify = require('gulp-notify');
|
||||
var gutil = require('gulp-util');
|
||||
var package = require('./package.json');
|
||||
var shell = require('gulp-shell');
|
||||
var reactify = require('reactify');
|
||||
|
||||
// The task that handles both development and deployment
|
||||
var runBrowserifyTask = function (options) {
|
||||
|
||||
// This bundle is for our application
|
||||
var bundler = browserify({
|
||||
debug: options.debug, // Need that sourcemapping
|
||||
standalone: 'Formsy',
|
||||
// These options are just for Watchify
|
||||
cache: {}, packageCache: {}, fullPaths: true
|
||||
})
|
||||
.require(require.resolve('./src/main.js'), { entry: true })
|
||||
.transform(reactify) // Transform JSX
|
||||
.external('react');
|
||||
|
||||
// The actual rebundle process
|
||||
var rebundle = function () {
|
||||
var start = Date.now();
|
||||
bundler.bundle()
|
||||
.on('error', gutil.log)
|
||||
.pipe(source(options.name))
|
||||
.pipe(gulpif(options.uglify, streamify(uglify())))
|
||||
.pipe(gulp.dest(options.dest))
|
||||
.pipe(notify(function () {
|
||||
|
||||
// Fix for requirejs
|
||||
var fs = require('fs');
|
||||
var file = fs.readFileSync(options.dest + '/' + options.name).toString();
|
||||
file = file.replace('define([],e)', 'define(["react"],e)');
|
||||
fs.writeFileSync(options.dest + '/' + options.name, file);
|
||||
|
||||
console.log('Built in ' + (Date.now() - start) + 'ms');
|
||||
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
// Fire up Watchify when developing
|
||||
if (options.watch) {
|
||||
bundler = watchify(bundler);
|
||||
bundler.on('update', rebundle);
|
||||
}
|
||||
|
||||
return rebundle();
|
||||
|
||||
};
|
||||
|
||||
gulp.task('default', function () {
|
||||
|
||||
runBrowserifyTask({
|
||||
watch: true,
|
||||
dest: './build',
|
||||
uglify: false,
|
||||
debug: true,
|
||||
name: 'formsy-react.js'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
gulp.task('deploy', function () {
|
||||
|
||||
runBrowserifyTask({
|
||||
watch: false,
|
||||
dest: './releases/' + package.version,
|
||||
uglify: true,
|
||||
debug: false,
|
||||
name: 'formsy-react-' + package.version + '.min.js'
|
||||
});
|
||||
|
||||
runBrowserifyTask({
|
||||
watch: false,
|
||||
dest: './releases/' + package.version,
|
||||
uglify: false,
|
||||
debug: false,
|
||||
name: 'formsy-react-' + package.version + '.js'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
gulp.task('test', shell.task([
|
||||
'./node_modules/.bin/jasmine-node ./specs --autotest --watch ./src --color'
|
||||
]));
|
||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Christian Alfoni
|
||||
Copyright (c) 2014-2016 PatientSky A/S
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
511
README.md
511
README.md
|
|
@ -1,146 +1,112 @@
|
|||
formsy-react
|
||||
formsy-react [](https://github.com/christianalfoni/formsy-react/releases) [](https://travis-ci.org/christianalfoni/formsy-react)
|
||||
============
|
||||
|
||||
A form input builder and validator for React JS
|
||||
|
||||
- [Background](#background)
|
||||
- [What you can do](#whatyoucando)
|
||||
- [Install](#install)
|
||||
- [Changes](#changes)
|
||||
- [How to use](#howtouse)
|
||||
- [API](#API)
|
||||
- [Formsy.defaults](#formsydefaults)
|
||||
- [Formsy.Form](#formsyform)
|
||||
- [className](#classname)
|
||||
- [url](#url)
|
||||
- [method](#method)
|
||||
- [contentType](#contenttype)
|
||||
- [hideSubmit](#hideSubmit)
|
||||
- [submitButtonClass](#submitButtonClass)
|
||||
- [cancelButtonClass](#cancelButtonClass)
|
||||
- [buttonWrapperClass](#buttonWrapperClass)
|
||||
- [onSuccess()](#onsuccess)
|
||||
- [onSubmit()](#onsubmit)
|
||||
- [onSubmitted()](#onsubmitted)
|
||||
- [onCancel()](#oncancel)
|
||||
- [onError()](#onerror)
|
||||
- [Formsy.Mixin](#formsymixin)
|
||||
- [name](#name)
|
||||
- [validations](#validations)
|
||||
- [validationError](#validationerror)
|
||||
- [required](#required)
|
||||
- [getValue()](#getvalue)
|
||||
- [setValue()](#setvalue)
|
||||
- [hasValue()](#hasvalue)
|
||||
- [resetValue()](#resetvalue)
|
||||
- [getErrorMessage()](#geterrormessage)
|
||||
- [isValid()](#isvalid)
|
||||
- [isRequired()](#isrequired)
|
||||
- [showRequired()](#showrequired)
|
||||
- [showError()](#showerror)
|
||||
- [Formsy.addValidationRule](#formsyaddvalidationrule)
|
||||
- [Validators](#validators)
|
||||
|
||||
| [How to use](#how-to-use) | [API](/API.md) | [Examples](/examples) |
|
||||
|---|---|---|
|
||||
|
||||
## <a name="background">Background</a>
|
||||
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
|
||||
I wrote an article on forms and validation with React JS, [Nailing that validation with React JS](http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html), the result of that was this extension.
|
||||
|
||||
The main concept is that forms, inputs and validation is done very differently across developers and projects. This extension to React JS aims to be that "sweet spot" between flexibility and reusability.
|
||||
|
||||
## <a name="whatyoucando">What you can do</a>
|
||||
## What you can do
|
||||
|
||||
1. Build any kind of form input components. Not just traditional inputs, but anything you want and get that validation for free
|
||||
1. Build any kind of form element components. Not just traditional inputs, but anything you want and get that validation for free
|
||||
|
||||
2. Add validation rules and use them with simple syntax
|
||||
|
||||
3. Use handlers for different states of your form. Ex. "onSubmit", "onError" etc.
|
||||
3. Use handlers for different states of your form. Ex. "onSubmit", "onError", "onValid" etc.
|
||||
|
||||
4. Server validation errors automatically binds to the correct form input component
|
||||
4. Pass external errors to the form to invalidate elements
|
||||
|
||||
## <a name="install">Install</a>
|
||||
5. You can dynamically add form elements to your form and they will register/unregister to the form
|
||||
|
||||
## Default elements
|
||||
You can look at examples in this repo or use the [formsy-react-components](https://github.com/twisty/formsy-react-components) project to use bootstrap with formsy-react, or use [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) to use [Material-UI](http://material-ui.com/) with formsy-react.
|
||||
|
||||
## Install
|
||||
|
||||
1. Download from this REPO and use globally (Formsy) or with requirejs
|
||||
2. Install with `npm install formsy-react` and use with browserify etc.
|
||||
3. Install with `bower install formsy-react`
|
||||
|
||||
## <a name="changes">Changes</a>
|
||||
## Changes
|
||||
|
||||
**0.2.2**:
|
||||
|
||||
- Fixed bug with updating the props
|
||||
[Check out releases](https://github.com/christianalfoni/formsy-react/releases)
|
||||
|
||||
**0.2.1**:
|
||||
|
||||
- Cancel button displays if onCancel handler is defined
|
||||
[Older changes](CHANGES.md)
|
||||
|
||||
**0.2.0**:
|
||||
|
||||
- Implemented hasValue() method
|
||||
## How to use
|
||||
|
||||
**0.1.3**:
|
||||
|
||||
- Fixed resetValue bug
|
||||
See [`examples` folder](/examples) for examples. [Codepen demo](http://codepen.io/semigradsky/pen/dYYpwv?editors=001).
|
||||
|
||||
**0.1.2**:
|
||||
|
||||
- Fixed isValue check to empty string, needs to support false and 0
|
||||
|
||||
**0.1.1**:
|
||||
|
||||
- Added resetValue method
|
||||
- Changed value check of showRequired
|
||||
|
||||
## <a name="howtouse">How to use</a>
|
||||
Complete API reference is available [here](/API.md).
|
||||
|
||||
#### Formsy gives you a form straight out of the box
|
||||
|
||||
```javascript
|
||||
/** @jsx React.DOM */
|
||||
var Formsy = require('formsy-react');
|
||||
var MyAppForm = React.createClass({
|
||||
changeUrl: function () {
|
||||
location.href = '/success';
|
||||
```jsx
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyAppForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
canSubmit: false
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
enableButton() {
|
||||
this.setState({
|
||||
canSubmit: true
|
||||
});
|
||||
},
|
||||
disableButton() {
|
||||
this.setState({
|
||||
canSubmit: false
|
||||
});
|
||||
},
|
||||
submit(model) {
|
||||
someDep.saveEmail(model.email);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form url="/users" onSuccess={this.changeUrl}>
|
||||
|
||||
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
|
||||
<MyOwnInput name="email" validations="isEmail" validationError="This is not a valid email" required/>
|
||||
|
||||
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
This code results in a form with a submit button that will POST to /users when clicked. The submit button is disabled as long as the input is empty (required) or the value is not an email (isEmail). On validation error it will show the message: "This is not a valid email".
|
||||
This code results in a form with a submit button that will run the `submit` method when the submit button is clicked with a valid email. The submit button is disabled as long as the input is empty ([required](/API.md#required)) or the value is not an email ([isEmail](/API.md#validators)). On validation error it will show the message: "This is not a valid email".
|
||||
|
||||
#### This is an example of what you can enjoy building
|
||||
```javascript
|
||||
/** @jsx React.DOM */
|
||||
var Formsy = require('formsy-react');
|
||||
var MyOwnInput = React.createClass({
|
||||
#### Building a form element (required)
|
||||
```jsx
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyOwnInput = React.createClass({
|
||||
|
||||
// Add the Formsy Mixin
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
// setValue() will set the value of the component, which in
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
changeValue: function (event) {
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
|
||||
render() {
|
||||
// Set a specific className based on the validation
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
var className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
|
||||
const className = this.showRequired() ? 'required' : this.showError() ? 'error' : null;
|
||||
|
||||
// An error message is returned ONLY if the component is invalid
|
||||
// or the server has returned an error message
|
||||
var errorMessage = this.getErrorMessage();
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
|
|
@ -151,355 +117,22 @@ This code results in a form with a submit button that will POST to /users when c
|
|||
}
|
||||
});
|
||||
```
|
||||
So this is basically how you build your form elements. As you can see it is very flexible, you just have a small API to help you identify the state of the component and set its value.
|
||||
The form element component is what gives the form validation functionality to whatever you want to put inside this wrapper. You do not have to use traditional inputs, it can be anything you want and the value of the form element can also be anything you want. As you can see it is very flexible, you just have a small API to help you identify the state of the component and set its value.
|
||||
|
||||
## <a name="API">API</a>
|
||||
## Related projects
|
||||
- [formsy-material-ui](https://github.com/mbrookes/formsy-material-ui) - A formsy-react compatibility wrapper for [Material-UI](http://material-ui.com/) form components.
|
||||
- [formsy-react-components](https://github.com/twisty/formsy-react-components) - A set of React JS components for use in a formsy-react form.
|
||||
- ...
|
||||
- Send PR for adding your project to this list!
|
||||
|
||||
### <a name="formsydefaults">Formsy.defaults(options)</a>
|
||||
```javascript
|
||||
Formsy.defaults({
|
||||
contentType: 'urlencoded', // default: 'json'
|
||||
hideSubmit: true, // default: false
|
||||
submitButtonClass: 'btn btn-success', // default: null
|
||||
cancelButtonClass: 'btn btn-default', // default: null
|
||||
buttonWrapperClass: 'my-wrapper' // default: null
|
||||
});
|
||||
```
|
||||
Use **defaults** to set general settings for all your forms.
|
||||
## Contribute
|
||||
- Fork repo
|
||||
- `npm install`
|
||||
- `npm run examples` runs the development server on `localhost:8080`
|
||||
- `npm test` runs the tests
|
||||
|
||||
### <a name="formsyform">Formsy.Form</a>
|
||||
## License
|
||||
|
||||
#### <a name="classname">className</a>
|
||||
```html
|
||||
<Formsy.Form className="my-class"></Formsy.Form>
|
||||
```
|
||||
Sets a class name on the form itself.
|
||||
[The MIT License (MIT)](/LICENSE)
|
||||
|
||||
#### <a name="url">url</a>
|
||||
```html
|
||||
<Formsy.Form url="/users"></Formsy.Form>
|
||||
```
|
||||
Will either **POST** or **PUT** to the url specified when submitted.
|
||||
|
||||
#### <a name="method">method</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT"></Formsy.Form>
|
||||
```
|
||||
Supports **POST** (default) and **PUT**.
|
||||
|
||||
#### <a name="contenttype">contentType</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT" contentType="urlencoded"></Formsy.Form>
|
||||
```
|
||||
Supports **json** (default) and **urlencoded** (x-www-form-urlencoded).
|
||||
|
||||
**Note!** Response has to be **json**.
|
||||
|
||||
#### <a name="hidesubmit">hideSubmit</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT" hideSubmit></Formsy.Form>
|
||||
```
|
||||
Hides the submit button. Submit is done by ENTER on an input.
|
||||
|
||||
#### <a name="submitbuttonclass">submitButtonClass</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT" submitButtonClass="btn btn-success"></Formsy.Form>
|
||||
```
|
||||
Sets a class name on the submit button.
|
||||
|
||||
#### <a name="cancelbuttonclass">cancelButtonClass</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT" cancelButtonClass="btn btn-default"></Formsy.Form>
|
||||
```
|
||||
Sets a class name on the cancel button.
|
||||
|
||||
#### <a name="buttonwrapperclass">buttonWrapperClass</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" method="PUT" buttonWrapperClass="my-wrapper"></Formsy.Form>
|
||||
```
|
||||
Sets a class name on the container that wraps the **submit** and **cancel** buttons.
|
||||
|
||||
#### <a name="onsuccess">onSuccess(serverResponse)</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onSuccess={this.changeUrl}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when the server has responded with a success http status code.
|
||||
|
||||
#### <a name="onsubmit">onSubmit()</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onSubmit={this.showFormLoader}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when the submit button has been clicked.
|
||||
|
||||
#### <a name="onsubmitted">onSubmitted()</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onSubmitted={this.hideFormLoader}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when either a success or error response is received from the server.
|
||||
|
||||
#### <a name="oncancel">onCancel()</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onCancel={this.goBack}></Formsy.Form>
|
||||
```
|
||||
Will display a "cancel" button next to submit. On click it runs the function handler.
|
||||
|
||||
#### <a name="onerror">onError(serverResponse)</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onError={this.changeToFormErrorClass}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when the server responds with an error http status code.
|
||||
|
||||
### <a name="formsymixin">Formsy.Mixin</a>
|
||||
|
||||
#### <a name="name">name</a>
|
||||
```html
|
||||
<MyInputComponent name="email"/>
|
||||
```
|
||||
The name is required to register the form input component in the form.
|
||||
|
||||
#### <a name="validations">validations</a>
|
||||
```html
|
||||
<MyInputComponent name="email" validations="isEmail"/>
|
||||
<MyInputComponent name="number" validations="isNumeric,isLength:5:12"/>
|
||||
```
|
||||
An comma seperated list with validation rules. Take a look at **Validators** to see default rules. Use ":" to separate arguments passed to the validator. The arguments will go through a **JSON.parse** converting them into correct JavaScript types. Meaning:
|
||||
|
||||
```html
|
||||
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange']"/>
|
||||
<MyInputComponent name="car" validations="mapsTo:{'bmw': true, 'vw': true}"/>
|
||||
```
|
||||
Works just fine.
|
||||
|
||||
#### <a name="validationerror">validationError</a>
|
||||
```html
|
||||
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email"/>
|
||||
```
|
||||
The message that will show when the form input component is invalid.
|
||||
|
||||
#### <a name="required">required</a>
|
||||
```html
|
||||
<MyInputComponent name="email" validations="isEmail" validationError="This is not an email" required/>
|
||||
```
|
||||
A property that tells the form that the form input component value is required.
|
||||
|
||||
#### <a name="getvalue">getValue()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
render: function () {
|
||||
return (
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Gets the current value of the form input component.
|
||||
|
||||
#### <a name="setvalue">setValue(value)</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc.
|
||||
|
||||
#### <a name="hasvalue">hasValue()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
{this.hasValue() ? 'There is a value here' : 'No value entered yet'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
The hasValue() method helps you identify if there actually is a value or not. The only invalid value in Formsy is an empty string, "". All other values are valid as they could be something you want to send to the server. F.ex. the number zero (0), or false.
|
||||
|
||||
#### <a name="resetvalue">resetValue()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<button onClick={this.resetValue}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Resets to empty value.
|
||||
|
||||
#### <a name="geterrormessage">getErrorMessage()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Will return the server error mapped to the form input component or return the validation message set if the form input component is invalid. If no server error and form input component is valid it returns **null**.
|
||||
|
||||
#### <a name="isvalid">isValid()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var face = this.isValid() ? ':-)' : ':-(';
|
||||
return (
|
||||
<div>
|
||||
<span>{face}</span>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Returns the valid state of the form input component.
|
||||
|
||||
#### <a name="isrequired">isRequired()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<span>{this.props.label} {this.isRequired() ? '*' : null}</span>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Returns true if the required property has been passed.
|
||||
|
||||
#### <a name="showrequired">showRequired()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var className = this.showRequired() ? 'required' : '';
|
||||
return (
|
||||
<div className={className}>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Lets you check if the form input component should indicate if it is a required field. This happens when the form input component value is empty and the required prop has been passed.
|
||||
|
||||
#### <a name="showerror">showError()</a>
|
||||
```javascript
|
||||
var MyInput = React.createClass({
|
||||
changeValue: function (event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
render: function () {
|
||||
var className = this.showRequired() ? 'required' : this.showError() ? 'error' : '';
|
||||
return (
|
||||
<div className={className}>
|
||||
<input type="text" onChange={this.changeValue} value={this.getValue()}/>
|
||||
<span>{this.getErrorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
Lets you check if the form input component should indicate if there is an error. This happens if there is a form input component value and it is invalid or if a server error is received.
|
||||
|
||||
### <a name="formsyaddvalidationrule">Formsy.addValidationRule(name, ruleFunc)</a>
|
||||
An example:
|
||||
```javascript
|
||||
Formsy.addValidationRule('isFruit', function (value) {
|
||||
return ['apple', 'orange', 'pear'].indexOf(value) >= 0;
|
||||
});
|
||||
```
|
||||
```html
|
||||
<MyInputComponent name="fruit" validations="'isFruit"/>
|
||||
```
|
||||
Another example:
|
||||
```javascript
|
||||
Formsy.addValidationRule('isIn', function (value, array) {
|
||||
return array.indexOf(value) >= 0;
|
||||
});
|
||||
```
|
||||
```html
|
||||
<MyInputComponent name="fruit" validations="isIn:['apple', 'orange', 'pear']"/>
|
||||
```
|
||||
## Validators
|
||||
**isValue**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isValue"/>
|
||||
```
|
||||
Returns true if the value is thruthful
|
||||
|
||||
**isEmail**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isEmail"/>
|
||||
```
|
||||
Return true if it is an email
|
||||
|
||||
**isTrue**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isTrue"/>
|
||||
```
|
||||
Returns true if the value is the boolean true
|
||||
|
||||
**isNumeric**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isNumeric"/>
|
||||
```
|
||||
Returns true if string only contains numbers
|
||||
|
||||
**isAlpha**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isAlpha"/>
|
||||
```
|
||||
Returns true if string is only letters
|
||||
|
||||
**isLength:min**, **isLength:min:max**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isLength:8"/>
|
||||
<MyInputComponent name="foo" validations="isLength:5:12"/>
|
||||
```
|
||||
Returns true if the value length is the equal or more than minimum and equal or less than maximum, if maximum is passed
|
||||
|
||||
**equals:value**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="equals:4"/>
|
||||
```
|
||||
Return true if the value from input component matches value passed (==).
|
||||
Copyright (c) 2014-2016 PatientSky A/S
|
||||
|
|
|
|||
25
bower.json
25
bower.json
|
|
@ -1,8 +1,25 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.2.2",
|
||||
"version": "0.18.0",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/christianalfoni/formsy-react.git"
|
||||
},
|
||||
"main": "src/main.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"build/",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"react": "^0.11.2"
|
||||
}
|
||||
}
|
||||
"react": "^0.14.7 || ^15.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"form",
|
||||
"forms",
|
||||
"validation",
|
||||
"react-component"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
Formsy React Examples
|
||||
=====================
|
||||
|
||||
To run and development examples:
|
||||
|
||||
1. Clone this repo
|
||||
2. Run `npm install`
|
||||
3. Start the development server with `npm run examples`
|
||||
4. Point your browser to http://localhost:8080
|
||||
|
||||
|
||||
## Possible Issues
|
||||
|
||||
Examples might not run if you have an old node packages. Try clear [npm cache](https://docs.npmjs.com/cli/cache#details) and reinstall dependencies:
|
||||
```
|
||||
rm -rf node_modules
|
||||
npm cache clean
|
||||
npm install
|
||||
npm run examples
|
||||
```
|
||||
|
||||
If it is not helped try update your node.js and npm.
|
||||
|
||||
## Examples
|
||||
|
||||
1. [**Login**](login)
|
||||
|
||||
Two required fields with simple validation.
|
||||
|
||||
2. [**Custom Validation**](custom-validation)
|
||||
|
||||
One field with added validation rule (`Formsy.addValidationRule`) and one field with dynamically added validation and error messages.
|
||||
|
||||
3. [**Reset Values**](reset-values)
|
||||
|
||||
Reset text input, checkbox and select to their pristine values.
|
||||
|
||||
4. [**Dynamic Form Fields**](dynamic-form-fields)
|
||||
|
||||
Dynamically adding and removing fields to form.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyInput = React.createClass({
|
||||
|
||||
// Add the Formsy Mixin
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
// setValue() will set the value of the component, which in
|
||||
// turn will validate it and the rest of the form
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
},
|
||||
render() {
|
||||
|
||||
// Set a specific className based on the validation
|
||||
// state of this component. showRequired() is true
|
||||
// when the value is empty and the required prop is
|
||||
// passed to the input. showError() is true when the
|
||||
// value typed is invalid
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
|
||||
// An error message is returned ONLY if the component is invalid
|
||||
// or the server has returned an error message
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input
|
||||
type={this.props.type || 'text'}
|
||||
name={this.props.name}
|
||||
onChange={this.changeValue}
|
||||
value={this.getValue()}
|
||||
checked={this.props.type === 'checkbox' && this.getValue() ? 'checked' : null}
|
||||
/>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default MyInput;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
function contains(container, item, cmp) {
|
||||
for (const it of container) {
|
||||
if (cmp(it, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
getInitialState() {
|
||||
return { value: [], cmp: (a, b) => a === b };
|
||||
},
|
||||
componentDidMount() {
|
||||
const value = this.props.value || [];
|
||||
this.setValue(value);
|
||||
this.setState({ value: value, cmp: this.props.cmp || this.state.cmp });
|
||||
},
|
||||
|
||||
changeValue(value, event) {
|
||||
const checked = event.currentTarget.checked;
|
||||
|
||||
let newValue = [];
|
||||
if (checked) {
|
||||
newValue = this.state.value.concat(value);
|
||||
} else {
|
||||
newValue = this.state.value.filter(it => !this.state.cmp(it, value));
|
||||
}
|
||||
|
||||
this.setValue(newValue);
|
||||
this.setState({ value: newValue });
|
||||
},
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={name}>{title}</label>
|
||||
{items.map((item, i) => (
|
||||
<div key={i}>
|
||||
<input
|
||||
type="checkbox"
|
||||
name={name}
|
||||
onChange={this.changeValue.bind(this, item)}
|
||||
checked={contains(this.state.value, item, this.state.cmp)}
|
||||
/>
|
||||
<span>{JSON.stringify(item)}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MyRadioGroup;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MyRadioGroup = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
componentDidMount() {
|
||||
const value = this.props.value;
|
||||
this.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
|
||||
changeValue(value) {
|
||||
this.setValue(value);
|
||||
this.setState({ value });
|
||||
},
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const { name, title, items } = this.props;
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={name}>{title}</label>
|
||||
{items.map((item, i) => (
|
||||
<div key={i}>
|
||||
<input
|
||||
type="radio"
|
||||
name={name}
|
||||
onChange={this.changeValue.bind(this, item)}
|
||||
checked={this.state.value === item}
|
||||
/>
|
||||
<span>{item.toString()}</span>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MyRadioGroup;
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import React from 'react';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
const MySelect = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') +
|
||||
(this.showRequired() ? 'required' : this.showError() ? 'error' : '');
|
||||
const errorMessage = this.getErrorMessage();
|
||||
|
||||
const options = this.props.options.map((option, i) => (
|
||||
<option key={option.title+option.value} value={option.value}>
|
||||
{option.title}
|
||||
</option>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<select name={this.props.name} onChange={this.changeValue} value={this.getValue()}>
|
||||
{options}
|
||||
</select>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export default MySelect;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.custom-validation {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Formsy from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const validators = {
|
||||
time: {
|
||||
regexp: /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(:([0-5]?[0-9]))?$/,
|
||||
message: 'Not valid time'
|
||||
},
|
||||
decimal: {
|
||||
regexp: /(^\d*\.?\d*[0-9]+\d*$)|(^[0-9]+\d*\.\d*$)/,
|
||||
message: 'Please type decimal value'
|
||||
},
|
||||
binary: {
|
||||
regexp: /^([0-1])*$/,
|
||||
message: '10101000'
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule('isYearOfBirth', (values, value) => {
|
||||
value = parseInt(value);
|
||||
if (typeof value !== 'number') {
|
||||
return false;
|
||||
}
|
||||
return value < currentYear && value > currentYear - 130;
|
||||
});
|
||||
|
||||
const App = React.createClass({
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.submit} className="custom-validation">
|
||||
<MyInput name="year" title="Year of Birth" type="number" validations="isYearOfBirth" validationError="Please type your year of birth" />
|
||||
<DynamicInput name="dynamic" title="..." />
|
||||
<button type="submit">Submit</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const DynamicInput = React.createClass({
|
||||
mixins: [Formsy.Mixin],
|
||||
getInitialState() {
|
||||
return { validationType: 'time' };
|
||||
},
|
||||
changeValue(event) {
|
||||
this.setValue(event.currentTarget.value);
|
||||
},
|
||||
changeValidation(validationType) {
|
||||
this.setState({ validationType: validationType });
|
||||
this.setValue(this.getValue());
|
||||
},
|
||||
validate() {
|
||||
const value = this.getValue();
|
||||
console.log(value, this.state.validationType);
|
||||
return value ? validators[this.state.validationType].regexp.test(value) : true;
|
||||
},
|
||||
getCustomErrorMessage() {
|
||||
return this.showError() ? validators[this.state.validationType].message : '';
|
||||
},
|
||||
render() {
|
||||
const className = 'form-group' + (this.props.className || ' ') + (this.showRequired() ? 'required' : this.showError() ? 'error' : null);
|
||||
const errorMessage = this.getCustomErrorMessage();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={this.props.name}>{this.props.title}</label>
|
||||
<input type='text' name={this.props.name} onChange={this.changeValue} value={this.getValue() || ''}/>
|
||||
<span className='validation-error'>{errorMessage}</span>
|
||||
<Validations validationType={this.state.validationType} changeValidation={this.changeValidation}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const Validations = React.createClass({
|
||||
changeValidation(e) {
|
||||
this.props.changeValidation(e.target.value);
|
||||
},
|
||||
render() {
|
||||
const { validationType } = this.props;
|
||||
return (
|
||||
<fieldset onChange={this.changeValidation}>
|
||||
<legend>Validation Type</legend>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='time' defaultChecked={validationType === 'time'}/>Time
|
||||
</div>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='decimal' defaultChecked={validationType === 'decimal'}/>Decimal
|
||||
</div>
|
||||
<div>
|
||||
<input name='validationType' type='radio' value='binary' defaultChecked={validationType === 'binary'}/>Binary
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Custom Validation Example</title>
|
||||
<link href="../global.css" rel="stylesheet"/>
|
||||
<link href="app.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Custom Validation</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/custom-validation.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
.many-fields-conf {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.many-fields {
|
||||
width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.field {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.many-fields .form-group {
|
||||
width: calc(100% - 20px);
|
||||
float: left;
|
||||
}
|
||||
.many-fields .remove-field {
|
||||
margin-top: 30px;
|
||||
margin-left: 8px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
import MySelect from './../components/Select';
|
||||
import MyRadioGroup from './../components/RadioGroup';
|
||||
import MyMultiCheckboxSet from './../components/MultiCheckboxSet';
|
||||
|
||||
const Fields = props => {
|
||||
function onRemove(pos) {
|
||||
return event => {
|
||||
event.preventDefault();
|
||||
props.onRemove(pos);
|
||||
};
|
||||
}
|
||||
const foo = 'required';
|
||||
return (
|
||||
<div className="fields">
|
||||
{props.data.map((field, i) => (
|
||||
<div className="field" key={field.id}>
|
||||
{
|
||||
field.type === 'input' ?
|
||||
(
|
||||
<MyInput
|
||||
value=""
|
||||
name={`fields[${i}]`}
|
||||
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
|
||||
required={field.required}
|
||||
validations={field.validations}
|
||||
/>
|
||||
) :
|
||||
(
|
||||
<MySelect
|
||||
name={`fields[${i}]`}
|
||||
title={field.validations ? JSON.stringify(field.validations) : 'No validations'}
|
||||
required={field.required}
|
||||
validations={field.validations}
|
||||
options={[
|
||||
{title: '123', value: '123'},
|
||||
{title: 'some long text', value: 'some long text'},
|
||||
{title: '`empty string`', value: ''},
|
||||
{title: 'alpha42', value: 'alpha42'},
|
||||
{title: 'test@mail.com', value: 'test@mail.com'}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<a href="#" className="remove-field" onClick={onRemove(i)}>X</a>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState() {
|
||||
return { fields: [], canSubmit: false };
|
||||
},
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
addField(fieldData) {
|
||||
fieldData.validations = fieldData.validations.length ?
|
||||
fieldData.validations.reduce((a, b) => Object.assign({}, a, b)) :
|
||||
null;
|
||||
fieldData.id = Date.now();
|
||||
this.setState({ fields: this.state.fields.concat(fieldData) });
|
||||
},
|
||||
removeField(pos) {
|
||||
const fields = this.state.fields;
|
||||
this.setState({ fields: fields.slice(0, pos).concat(fields.slice(pos+1)) })
|
||||
},
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
},
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
},
|
||||
render() {
|
||||
const { fields, canSubmit } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<Form onSubmit={this.addField} className="many-fields-conf">
|
||||
<MyMultiCheckboxSet name="validations" title="Validations"
|
||||
cmp={(a, b) => JSON.stringify(a) === JSON.stringify(b)}
|
||||
items={[
|
||||
{isEmail: true},
|
||||
{isEmptyString: true},
|
||||
{isNumeric: true},
|
||||
{isAlphanumeric: true},
|
||||
{equals: 5},
|
||||
{minLength: 3},
|
||||
{maxLength: 7}
|
||||
]}
|
||||
/>
|
||||
<MyRadioGroup name="required" value={false} title="Required"
|
||||
items={[true, false]} />
|
||||
<MyRadioGroup name="type" value="input" title="Type"
|
||||
items={['input', 'select']} />
|
||||
<button type="submit">Add</button>
|
||||
</Form>
|
||||
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="many-fields">
|
||||
<Fields data={fields} onRemove={this.removeField} />
|
||||
<button type="submit" disabled={!canSubmit}>Submit</button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Form Fields</title>
|
||||
<link href="../global.css" rel="stylesheet"/>
|
||||
<link href="app.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Dynamic Form Fields</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/dynamic-form-fields.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
body {
|
||||
font-family: "Helvetica Neue", Arial;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
a {
|
||||
color: hsl(200, 50%, 50%);
|
||||
}
|
||||
|
||||
a.active {
|
||||
color: hsl(20, 50%, 50%);
|
||||
}
|
||||
|
||||
.breadcrumbs a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 15px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-top: 5px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form-group input[type='text'],
|
||||
.form-group input[type='email'],
|
||||
.form-group input[type='number'],
|
||||
.form-group input[type='password'],
|
||||
.form-group select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 34px;
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
line-height: 1.42857143;
|
||||
color: #555;
|
||||
background-color: #FFF;
|
||||
background-image: none;
|
||||
border: 2px solid #CCC;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group.error input[type='text'],
|
||||
.form-group.error input[type='email'],
|
||||
.form-group.error input[type='number'],
|
||||
.form-group.error input[type='password'],
|
||||
.form-group.error select {
|
||||
border-color: red;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.form-group.required input[type='text'],
|
||||
.form-group.required input[type='email'],
|
||||
.form-group.required input[type='number'],
|
||||
.form-group.required input[type='password'],
|
||||
.form-group.required select {
|
||||
border-color: #FF9696;
|
||||
}
|
||||
|
||||
.validation-error {
|
||||
color: red;
|
||||
margin: 5px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.buttons button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Formsy React Examples</title>
|
||||
<link href="global.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Formsy React Examples</h1>
|
||||
<ul>
|
||||
<li><a href="login/index.html">Login Page</a></li>
|
||||
<li><a href="custom-validation/index.html">Custom Validation</a></li>
|
||||
<li><a href="reset-values/index.html">Reset Values</a></li>
|
||||
<li><a href="dynamic-form-fields/index.html">Dynamic Form Fields</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.login {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
|
||||
const App = React.createClass({
|
||||
getInitialState() {
|
||||
return { canSubmit: false };
|
||||
},
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
enableButton() {
|
||||
this.setState({ canSubmit: true });
|
||||
},
|
||||
disableButton() {
|
||||
this.setState({ canSubmit: false });
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Form onSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton} className="login">
|
||||
<MyInput value="" name="email" title="Email" validations="isEmail" validationError="This is not a valid email" required />
|
||||
<MyInput value="" name="password" title="Password" type="password" required />
|
||||
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Login Example</title>
|
||||
<link href="../global.css" rel="stylesheet"/>
|
||||
<link href="app.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Login</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/login.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.form {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Form } from 'formsy-react';
|
||||
|
||||
import MyInput from './../components/Input';
|
||||
import MySelect from './../components/Select';
|
||||
|
||||
const user = {
|
||||
name: 'Sam',
|
||||
free: true,
|
||||
hair: 'brown'
|
||||
};
|
||||
|
||||
const App = React.createClass({
|
||||
submit(data) {
|
||||
alert(JSON.stringify(data, null, 4));
|
||||
},
|
||||
resetForm() {
|
||||
this.refs.form.reset();
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="form" onSubmit={this.submit} className="form">
|
||||
<MyInput name="name" title="Name" value={user.name} />
|
||||
<MyInput name="free" title="Free to hire" type="checkbox" value={user.free} />
|
||||
<MySelect name="hair" title="Hair" value={user.hair}
|
||||
options={[
|
||||
{ value: "black", title: "Black" },
|
||||
{ value: "brown", title: "Brown" },
|
||||
{ value: "blonde", title: "Blonde" },
|
||||
{ value: "red", title: "Red" }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="buttons">
|
||||
<button type="reset" onClick={this.resetForm}>Reset</button>
|
||||
<button type="submit">Submit</button>
|
||||
</div>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('example'));
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Reset Values</title>
|
||||
<link href="../global.css" rel="stylesheet"/>
|
||||
<link href="app.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="breadcrumbs"><a href="../index.html">Formsy React Examples</a> / Reset Values</h1>
|
||||
<div id="example"/>
|
||||
<script src="/__build__/shared.js"></script>
|
||||
<script src="/__build__/reset-values.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var webpack = require('webpack');
|
||||
|
||||
function isDirectory(dir) {
|
||||
return fs.lstatSync(dir).isDirectory();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
devtool: 'inline-source-map',
|
||||
|
||||
entry: fs.readdirSync(__dirname).reduce(function (entries, dir) {
|
||||
var isDraft = dir.charAt(0) === '_' || dir.indexOf('components') >= 0;
|
||||
|
||||
if (!isDraft && isDirectory(path.join(__dirname, dir))) {
|
||||
entries[dir] = path.join(__dirname, dir, 'app.js');
|
||||
}
|
||||
|
||||
return entries;
|
||||
}, {}),
|
||||
|
||||
output: {
|
||||
path: 'examples/__build__',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
publicPath: '/__build__/'
|
||||
},
|
||||
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' }
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'formsy-react': '../../src/main'
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.optimize.CommonsChunkPlugin('shared.js'),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
|
||||
})
|
||||
]
|
||||
|
||||
};
|
||||
54
package.json
54
package.json
|
|
@ -1,25 +1,49 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.2.2",
|
||||
"version": "0.19.5",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"main": "src/main.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/christianalfoni/formsy-react.git"
|
||||
},
|
||||
"main": "lib/main.js",
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/jasmine-node ./specs"
|
||||
"build": "NODE_ENV=production webpack -p --config webpack.production.config.js",
|
||||
"test": "babel-node testrunner",
|
||||
"examples": "webpack-dev-server --config examples/webpack.config.js --content-base examples",
|
||||
"prepublish": "babel ./src/ -d ./lib/"
|
||||
},
|
||||
"author": "Christian Alfoni",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"react",
|
||||
"form",
|
||||
"forms",
|
||||
"validation",
|
||||
"react-component"
|
||||
],
|
||||
"dependencies": {
|
||||
"form-data-to-object": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^5.11.2",
|
||||
"gulp": "^3.8.8",
|
||||
"gulp-if": "^1.2.4",
|
||||
"gulp-notify": "^1.6.0",
|
||||
"gulp-shell": "^0.2.9",
|
||||
"gulp-streamify": "0.0.5",
|
||||
"gulp-uglify": "^1.0.1",
|
||||
"gulp-util": "^3.0.1",
|
||||
"react": "^0.11.2",
|
||||
"reactify": "^0.14.0",
|
||||
"vinyl-source-stream": "^1.0.0",
|
||||
"watchify": "^1.0.2"
|
||||
"babel-cli": "^6.6.5",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-2": "^6.5.0",
|
||||
"create-react-class": "^15.6.0",
|
||||
"jsdom": "^6.5.1",
|
||||
"nodeunit": "^0.9.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.0.0",
|
||||
"react-addons-pure-render-mixin": "^15.0.0",
|
||||
"react-addons-test-utils": "^15.0.0",
|
||||
"react-dom": "^15.0.0",
|
||||
"sinon": "^1.17.3",
|
||||
"webpack": "^1.12.14",
|
||||
"webpack-dev-server": "^1.14.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^0.14.0 || ^15.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,341 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return !!value;
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && !this.state._value.length;
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value) {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.resetButtonClass || options.resetButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.showCancel || options.showCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,346 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return !!value;
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value) {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.resetButtonClass || options.resetButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.showCancel || options.showCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,346 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value !== '') {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.resetButtonClass || options.resetButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.showCancel || options.showCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,348 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value !== '') {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.resetButtonClass || options.resetButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.showCancel || options.showCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,351 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value !== '') {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.resetButtonClass || options.resetButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.showCancel || options.showCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,351 +0,0 @@
|
|||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(["react"],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Formsy=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({"/Users/christianalfoni/Documents/dev/formsy-react/src/main.js":[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var options = {};
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
}
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
// validate the input and set its state. Then check the
|
||||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value !== '') {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.cancelButtonClass || options.cancelButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.onCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (!global.exports && !global.module && (!global.define || !global.define.amd)) {
|
||||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,30 @@
|
|||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Mixin = require('./Mixin.js');
|
||||
module.exports = function () {
|
||||
return function (Component) {
|
||||
return createReactClass({
|
||||
mixins: [Mixin],
|
||||
render: function () {
|
||||
return React.createElement(Component, {
|
||||
setValidations: this.setValidations,
|
||||
setValue: this.setValue,
|
||||
resetValue: this.resetValue,
|
||||
getValue: this.getValue,
|
||||
hasValue: this.hasValue,
|
||||
getErrorMessage: this.getErrorMessage,
|
||||
getErrorMessages: this.getErrorMessages,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValid: this.isValid,
|
||||
isPristine: this.isPristine,
|
||||
isFormSubmitted: this.isFormSubmitted,
|
||||
isRequired: this.isRequired,
|
||||
showRequired: this.showRequired,
|
||||
showError: this.showError,
|
||||
isValidValue: this.isValidValue,
|
||||
...this.props
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Mixin = require('./Mixin.js');
|
||||
module.exports = function (Component) {
|
||||
return createReactClass({
|
||||
displayName: 'Formsy(' + getDisplayName(Component) + ')',
|
||||
mixins: [Mixin],
|
||||
|
||||
render: function () {
|
||||
const { innerRef } = this.props;
|
||||
const propsForElement = {
|
||||
setValidations: this.setValidations,
|
||||
setValue: this.setValue,
|
||||
resetValue: this.resetValue,
|
||||
getValue: this.getValue,
|
||||
hasValue: this.hasValue,
|
||||
getErrorMessage: this.getErrorMessage,
|
||||
getErrorMessages: this.getErrorMessages,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValid: this.isValid,
|
||||
isPristine: this.isPristine,
|
||||
isFormSubmitted: this.isFormSubmitted,
|
||||
isRequired: this.isRequired,
|
||||
showRequired: this.showRequired,
|
||||
showError: this.showError,
|
||||
isValidValue: this.isValidValue,
|
||||
...this.props
|
||||
};
|
||||
|
||||
if (innerRef) {
|
||||
propsForElement.ref = innerRef;
|
||||
}
|
||||
return React.createElement(Component, propsForElement);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function getDisplayName(Component) {
|
||||
return (
|
||||
Component.displayName ||
|
||||
Component.name ||
|
||||
(typeof Component === 'string' ? Component : 'Component')
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
var PropTypes = require('prop-types');
|
||||
var utils = require('./utils.js');
|
||||
var React = global.React || require('react');
|
||||
|
||||
var convertValidationsToObject = function (validations) {
|
||||
|
||||
if (typeof validations === 'string') {
|
||||
|
||||
return validations.split(/\,(?![^{\[]*[}\]])/g).reduce(function (validations, validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
|
||||
args = args.map(function (arg) {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
} catch (e) {
|
||||
return arg; // It is a string if it can not parse it
|
||||
}
|
||||
});
|
||||
|
||||
if (args.length > 1) {
|
||||
throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
|
||||
}
|
||||
|
||||
validations[validateMethod] = args.length ? args[0] : true;
|
||||
return validations;
|
||||
}, {});
|
||||
|
||||
}
|
||||
|
||||
return validations || {};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value,
|
||||
_isRequired: false,
|
||||
_isValid: true,
|
||||
_isPristine: true,
|
||||
_pristineValue: this.props.value,
|
||||
_validationError: [],
|
||||
_externalError: null,
|
||||
_formSubmitted: false
|
||||
};
|
||||
},
|
||||
contextTypes: {
|
||||
formsy: PropTypes.object // What about required?
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
validationError: '',
|
||||
validationErrors: {}
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function () {
|
||||
var configure = function () {
|
||||
this.setValidations(this.props.validations, this.props.required);
|
||||
|
||||
// Pass a function instead?
|
||||
this.context.formsy.attachToForm(this);
|
||||
//this.props._attachToForm(this);
|
||||
}.bind(this);
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
/*
|
||||
if (!this.props._attachToForm) {
|
||||
return setTimeout(function () {
|
||||
if (!this.isMounted()) return;
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
configure();
|
||||
}.bind(this), 0);
|
||||
}
|
||||
*/
|
||||
configure();
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
this.setValidations(nextProps.validations, nextProps.required);
|
||||
|
||||
},
|
||||
|
||||
componentDidUpdate: function (prevProps) {
|
||||
|
||||
// If the value passed has changed, set it. If value is not passed it will
|
||||
// internally update, and this will never run
|
||||
if (!utils.isSame(this.props.value, prevProps.value)) {
|
||||
this.setValue(this.props.value);
|
||||
}
|
||||
|
||||
// If validations or required is changed, run a new validation
|
||||
if (!utils.isSame(this.props.validations, prevProps.validations) || !utils.isSame(this.props.required, prevProps.required)) {
|
||||
this.context.formsy.validate(this);
|
||||
}
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.context.formsy.detachFromForm(this);
|
||||
//this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
setValidations: function (validations, required) {
|
||||
|
||||
// Add validations to the store itself as the props object can not be modified
|
||||
this._validations = convertValidationsToObject(validations) || {};
|
||||
this._requiredValidations = required === true ? {isDefaultRequiredValue: true} : convertValidationsToObject(required);
|
||||
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value,
|
||||
_isPristine: false
|
||||
}, function () {
|
||||
this.context.formsy.validate(this);
|
||||
//this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: this.state._pristineValue,
|
||||
_isPristine: true
|
||||
}, function () {
|
||||
this.context.formsy.validate(this);
|
||||
//this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
var messages = this.getErrorMessages();
|
||||
return messages.length ? messages[0] : null;
|
||||
},
|
||||
getErrorMessages: function () {
|
||||
return !this.isValid() || this.showRequired() ? (this.state._externalError || this.state._validationError || []) : [];
|
||||
},
|
||||
isFormDisabled: function () {
|
||||
return this.context.formsy.isFormDisabled();
|
||||
//return this.props._isFormDisabled();
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isPristine: function () {
|
||||
return this.state._isPristine;
|
||||
},
|
||||
isFormSubmitted: function () {
|
||||
return this.state._formSubmitted;
|
||||
},
|
||||
isRequired: function () {
|
||||
return !!this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.state._isRequired;
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.isValid();
|
||||
},
|
||||
isValidValue: function (value) {
|
||||
return this.context.formsy.isValidValue.call(null, this, value);
|
||||
//return this.props._isValidValue.call(null, this, value);
|
||||
}
|
||||
};
|
||||
616
src/main.js
616
src/main.js
|
|
@ -1,164 +1,35 @@
|
|||
var PropTypes = require('prop-types');
|
||||
var React = global.React || require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return value.match(/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
'isTrue': function (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= min && value.length <= max;
|
||||
}
|
||||
return value.length >= min;
|
||||
},
|
||||
equals: function (value, eql) {
|
||||
return value == eql;
|
||||
}
|
||||
};
|
||||
var toURLEncoded = function (element,key,list){
|
||||
var list = list || [];
|
||||
if(typeof(element)=='object'){
|
||||
for (var idx in element)
|
||||
toURLEncoded(element[idx],key?key+'['+idx+']':idx,list);
|
||||
} else {
|
||||
list.push(key+'='+encodeURIComponent(element));
|
||||
}
|
||||
return list.join('&');
|
||||
};
|
||||
|
||||
var request = function (method, url, data, contentType) {
|
||||
|
||||
var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json';
|
||||
data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data);
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.setRequestHeader('Content-Type', contentType);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
} else {
|
||||
reject(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
var ajax = {
|
||||
post: request.bind(null, 'POST'),
|
||||
put: request.bind(null, 'PUT')
|
||||
};
|
||||
var validationRules = require('./validationRules.js');
|
||||
var formDataToObject = require('form-data-to-object');
|
||||
var utils = require('./utils.js');
|
||||
var Mixin = require('./Mixin.js');
|
||||
var HOC = require('./HOC.js');
|
||||
var Decorator = require('./Decorator.js');
|
||||
var options = {};
|
||||
var emptyArray = [];
|
||||
|
||||
Formsy.Mixin = Mixin;
|
||||
Formsy.HOC = HOC;
|
||||
Formsy.Decorator = Decorator;
|
||||
|
||||
Formsy.defaults = function (passedOptions) {
|
||||
options = passedOptions;
|
||||
};
|
||||
|
||||
Formsy.Mixin = {
|
||||
getInitialState: function () {
|
||||
return {
|
||||
_value: this.props.value ? this.props.value : '',
|
||||
_isValid: true
|
||||
};
|
||||
},
|
||||
componentWillMount: function () {
|
||||
|
||||
if (!this.props.name) {
|
||||
throw new Error('Form Input requires a name property when used');
|
||||
}
|
||||
|
||||
if (!this.props._attachToForm) {
|
||||
throw new Error('Form Mixin requires component to be nested in a Form');
|
||||
}
|
||||
|
||||
if (this.props.required) {
|
||||
this.props.validations = this.props.validations ? this.props.validations + ',' : '';
|
||||
this.props.validations += 'isValue';
|
||||
}
|
||||
this.props._attachToForm(this);
|
||||
},
|
||||
|
||||
// We have to make the validate method is kept when new props are added
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
nextProps._attachToForm = this.props._attachToForm;
|
||||
nextProps._detachFromForm = this.props._detachFromForm;
|
||||
nextProps._validate = this.props._validate;
|
||||
},
|
||||
|
||||
// Detach it when component unmounts
|
||||
componentWillUnmount: function () {
|
||||
this.props._detachFromForm(this);
|
||||
},
|
||||
|
||||
// We validate after the value has been set
|
||||
setValue: function (value) {
|
||||
this.setState({
|
||||
_value: value
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
}.bind(this));
|
||||
},
|
||||
resetValue: function () {
|
||||
this.setState({
|
||||
_value: ''
|
||||
}, function () {
|
||||
this.props._validate(this);
|
||||
});
|
||||
},
|
||||
getValue: function () {
|
||||
return this.state._value;
|
||||
},
|
||||
hasValue: function () {
|
||||
return this.state._value !== '';
|
||||
},
|
||||
getErrorMessage: function () {
|
||||
return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError;
|
||||
},
|
||||
isValid: function () {
|
||||
return this.state._isValid;
|
||||
},
|
||||
isRequired: function () {
|
||||
return this.props.required;
|
||||
},
|
||||
showRequired: function () {
|
||||
return this.props.required && this.state._value === '';
|
||||
},
|
||||
showError: function () {
|
||||
return !this.showRequired() && !this.state._isValid;
|
||||
}
|
||||
};
|
||||
|
||||
Formsy.addValidationRule = function (name, func) {
|
||||
validationRules[name] = func;
|
||||
};
|
||||
|
||||
Formsy.Form = React.createClass({
|
||||
Formsy.Form = createReactClass({
|
||||
displayName: 'Formsy',
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isValid: true,
|
||||
isSubmitting: false
|
||||
isSubmitting: false,
|
||||
canChange: false
|
||||
};
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
|
|
@ -166,91 +37,188 @@ Formsy.Form = React.createClass({
|
|||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {}
|
||||
onValidSubmit: function () {},
|
||||
onInvalidSubmit: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: function () {},
|
||||
onChange: function () {},
|
||||
validationErrors: null,
|
||||
preventExternalInvalidation: false
|
||||
};
|
||||
},
|
||||
|
||||
childContextTypes: {
|
||||
formsy: PropTypes.object
|
||||
},
|
||||
getChildContext: function () {
|
||||
return {
|
||||
formsy: {
|
||||
attachToForm: this.attachToForm,
|
||||
detachFromForm: this.detachFromForm,
|
||||
validate: this.validate,
|
||||
isFormDisabled: this.isFormDisabled,
|
||||
isValidValue: (component, value) => {
|
||||
return this.runValidation(component, value).isValid;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Add a map to store the inputs of the form, a model to store
|
||||
// the values of the form and register child inputs
|
||||
componentWillMount: function () {
|
||||
this.inputs = {};
|
||||
this.model = {};
|
||||
this.registerInputs(this.props.children);
|
||||
this.inputs = [];
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.props.url) {
|
||||
throw new Error('Formsy Form needs a url property to post the form');
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
this.props.onSubmit();
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json')
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
this.onSubmitted();
|
||||
}.bind(this))
|
||||
.catch(this.updateInputsWithError);
|
||||
componentWillUpdate: function () {
|
||||
// Keep a reference to input names before form updates,
|
||||
// to check if inputs has changed after render
|
||||
this.prevInputNames = this.inputs.map(component => component.props.name);
|
||||
},
|
||||
|
||||
// Goes through all registered components and
|
||||
// updates the model values
|
||||
updateModel: function () {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
var component = this.inputs[name];
|
||||
this.model[name] = component.state._value;
|
||||
}.bind(this));
|
||||
componentDidUpdate: function () {
|
||||
|
||||
if (this.props.validationErrors && typeof this.props.validationErrors === 'object' && Object.keys(this.props.validationErrors).length > 0) {
|
||||
this.setInputValidationErrors(this.props.validationErrors);
|
||||
}
|
||||
|
||||
var newInputNames = this.inputs.map(component => component.props.name);
|
||||
if (utils.arraysDiffer(this.prevInputNames, newInputNames)) {
|
||||
this.validateForm();
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Allow resetting to specified data
|
||||
reset: function (data) {
|
||||
this.setFormPristine(true);
|
||||
this.resetModel(data);
|
||||
},
|
||||
|
||||
// Update model, submit to url prop and send the model
|
||||
submit: function (event) {
|
||||
|
||||
event && event.preventDefault();
|
||||
|
||||
// Trigger form as not pristine.
|
||||
// If any inputs have not been touched yet this will make them dirty
|
||||
// so validation becomes visible (if based on isPristine)
|
||||
this.setFormPristine(false);
|
||||
var model = this.getModel();
|
||||
this.props.onSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
this.state.isValid ? this.props.onValidSubmit(model, this.resetModel, this.updateInputsWithError) : this.props.onInvalidSubmit(model, this.resetModel, this.updateInputsWithError);
|
||||
|
||||
},
|
||||
|
||||
mapModel: function (model) {
|
||||
|
||||
if (this.props.mapping) {
|
||||
return this.props.mapping(model)
|
||||
} else {
|
||||
return formDataToObject.toObj(Object.keys(model).reduce((mappedModel, key) => {
|
||||
|
||||
var keyArray = key.split('.');
|
||||
var base = mappedModel;
|
||||
while (keyArray.length) {
|
||||
var currentKey = keyArray.shift();
|
||||
base = (base[currentKey] = keyArray.length ? base[currentKey] || {} : model[key]);
|
||||
}
|
||||
|
||||
return mappedModel;
|
||||
|
||||
}, {}));
|
||||
}
|
||||
},
|
||||
|
||||
getModel: function () {
|
||||
var currentValues = this.getCurrentValues();
|
||||
return this.mapModel(currentValues);
|
||||
},
|
||||
|
||||
// Reset each key in the model to the original / initial / specified value
|
||||
resetModel: function (data) {
|
||||
this.inputs.forEach(component => {
|
||||
var name = component.props.name;
|
||||
if (data && data.hasOwnProperty(name)) {
|
||||
component.setValue(data[name]);
|
||||
} else {
|
||||
component.resetValue();
|
||||
}
|
||||
});
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
setInputValidationErrors: function (errors) {
|
||||
this.inputs.forEach(component => {
|
||||
var name = component.props.name;
|
||||
var args = [{
|
||||
_isValid: !(name in errors),
|
||||
_validationError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
|
||||
}];
|
||||
component.setState.apply(component, args);
|
||||
});
|
||||
},
|
||||
|
||||
// Checks if the values have changed from their initial value
|
||||
isChanged: function() {
|
||||
return !utils.isSame(this.getPristineValues(), this.getCurrentValues());
|
||||
},
|
||||
|
||||
getPristineValues: function() {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
var name = component.props.name;
|
||||
data[name] = component.props.value;
|
||||
return data;
|
||||
}, {});
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
// stored in the inputs map. Change their state to invalid
|
||||
// and set the serverError message
|
||||
updateInputsWithError: function (errors) {
|
||||
Object.keys(errors).forEach(function (name, index) {
|
||||
var component = this.inputs[name];
|
||||
var args = [{
|
||||
_isValid: false,
|
||||
_serverError: errors[name]
|
||||
}];
|
||||
if (index === Object.keys(errors).length - 1) {
|
||||
args.push(this.validateForm);
|
||||
Object.keys(errors).forEach((name, index) => {
|
||||
var component = utils.find(this.inputs, component => component.props.name === name);
|
||||
if (!component) {
|
||||
throw new Error('You are trying to update an input that does not exist. ' +
|
||||
'Verify errors object with input names. ' + JSON.stringify(errors));
|
||||
}
|
||||
var args = [{
|
||||
_isValid: this.props.preventExternalInvalidation || false,
|
||||
_externalError: typeof errors[name] === 'string' ? [errors[name]] : errors[name]
|
||||
}];
|
||||
component.setState.apply(component, args);
|
||||
}.bind(this));
|
||||
this.setState({
|
||||
isSubmitting: false
|
||||
});
|
||||
this.props.onError(errors);
|
||||
this.props.onSubmitted();
|
||||
},
|
||||
|
||||
// Traverse the children and children of children to find
|
||||
// all inputs by checking the name prop. Maybe do a better
|
||||
// check here
|
||||
registerInputs: function (children) {
|
||||
React.Children.forEach(children, function (child) {
|
||||
isFormDisabled: function () {
|
||||
return this.props.disabled;
|
||||
},
|
||||
|
||||
if (child.props.name) {
|
||||
child.props._attachToForm = this.attachToForm;
|
||||
child.props._detachFromForm = this.detachFromForm;
|
||||
child.props._validate = this.validate;
|
||||
}
|
||||
getCurrentValues: function () {
|
||||
return this.inputs.reduce((data, component) => {
|
||||
var name = component.props.name;
|
||||
data[name] = component.state._value;
|
||||
return data;
|
||||
}, {});
|
||||
},
|
||||
|
||||
if (child.props.children) {
|
||||
this.registerInputs(child.props.children);
|
||||
}
|
||||
setFormPristine: function (isPristine) {
|
||||
this.setState({
|
||||
_formSubmitted: !isPristine
|
||||
});
|
||||
|
||||
}.bind(this));
|
||||
// Iterate through each component and set it as pristine
|
||||
// or "dirty".
|
||||
this.inputs.forEach((component, index) => {
|
||||
component.setState({
|
||||
_formSubmitted: !isPristine,
|
||||
_isPristine: isPristine
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// Use the binded values and the actual input value to
|
||||
|
|
@ -258,91 +226,227 @@ Formsy.Form = React.createClass({
|
|||
// state of the form itself
|
||||
validate: function (component) {
|
||||
|
||||
if (!component.props.validations) {
|
||||
return;
|
||||
// Trigger onChange
|
||||
if (this.state.canChange) {
|
||||
this.props.onChange(this.getCurrentValues(), this.isChanged());
|
||||
}
|
||||
|
||||
var validation = this.runValidation(component);
|
||||
// Run through the validations, split them up and call
|
||||
// the validator IF there is a value or it is required
|
||||
var isValid = true;
|
||||
if (component.props.required || component.state._value !== '') {
|
||||
component.props.validations.split(',').forEach(function (validation) {
|
||||
var args = validation.split(':');
|
||||
var validateMethod = args.shift();
|
||||
args = args.map(function (arg) { return JSON.parse(arg); });
|
||||
args = [component.state._value].concat(args);
|
||||
if (!validationRules[validateMethod]) {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
|
||||
component.setState({
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error,
|
||||
_externalError: null
|
||||
}, this.validateForm);
|
||||
|
||||
},
|
||||
|
||||
// Checks validation on current value or a passed value
|
||||
runValidation: function (component, value) {
|
||||
|
||||
var currentValues = this.getCurrentValues();
|
||||
var validationErrors = component.props.validationErrors;
|
||||
var validationError = component.props.validationError;
|
||||
value = arguments.length === 2 ? value : component.state._value;
|
||||
|
||||
var validationResults = this.runRules(value, currentValues, component._validations);
|
||||
var requiredResults = this.runRules(value, currentValues, component._requiredValidations);
|
||||
|
||||
// the component defines an explicit validate function
|
||||
if (typeof component.validate === "function") {
|
||||
validationResults.failed = component.validate() ? [] : ['failed'];
|
||||
}
|
||||
|
||||
var isRequired = Object.keys(component._requiredValidations).length ? !!requiredResults.success.length : false;
|
||||
var isValid = !validationResults.failed.length && !(this.props.validationErrors && this.props.validationErrors[component.props.name]);
|
||||
|
||||
return {
|
||||
isRequired: isRequired,
|
||||
isValid: isRequired ? false : isValid,
|
||||
error: (function () {
|
||||
|
||||
if (isValid && !isRequired) {
|
||||
return emptyArray;
|
||||
}
|
||||
if (!validationRules[validateMethod].apply(null, args)) {
|
||||
isValid = false;
|
||||
|
||||
if (validationResults.errors.length) {
|
||||
return validationResults.errors;
|
||||
}
|
||||
|
||||
if (this.props.validationErrors && this.props.validationErrors[component.props.name]) {
|
||||
return typeof this.props.validationErrors[component.props.name] === 'string' ? [this.props.validationErrors[component.props.name]] : this.props.validationErrors[component.props.name];
|
||||
}
|
||||
|
||||
if (isRequired) {
|
||||
var error = validationErrors[requiredResults.success[0]];
|
||||
return error ? [error] : null;
|
||||
}
|
||||
|
||||
if (validationResults.failed.length) {
|
||||
return validationResults.failed.map(function(failed) {
|
||||
return validationErrors[failed] ? validationErrors[failed] : validationError;
|
||||
}).filter(function(x, pos, arr) {
|
||||
// Remove duplicates
|
||||
return arr.indexOf(x) === pos;
|
||||
});
|
||||
}
|
||||
|
||||
}.call(this))
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
runRules: function (value, currentValues, validations) {
|
||||
|
||||
var results = {
|
||||
errors: [],
|
||||
failed: [],
|
||||
success: []
|
||||
};
|
||||
if (Object.keys(validations).length) {
|
||||
Object.keys(validations).forEach(function (validationMethod) {
|
||||
|
||||
if (validationRules[validationMethod] && typeof validations[validationMethod] === 'function') {
|
||||
throw new Error('Formsy does not allow you to override default validations: ' + validationMethod);
|
||||
}
|
||||
|
||||
if (!validationRules[validationMethod] && typeof validations[validationMethod] !== 'function') {
|
||||
throw new Error('Formsy does not have the validation rule: ' + validationMethod);
|
||||
}
|
||||
|
||||
if (typeof validations[validationMethod] === 'function') {
|
||||
var validation = validations[validationMethod](currentValues, value);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
|
||||
} else if (typeof validations[validationMethod] !== 'function') {
|
||||
var validation = validationRules[validationMethod](currentValues, value, validations[validationMethod]);
|
||||
if (typeof validation === 'string') {
|
||||
results.errors.push(validation);
|
||||
results.failed.push(validationMethod);
|
||||
} else if (!validation) {
|
||||
results.failed.push(validationMethod);
|
||||
} else {
|
||||
results.success.push(validationMethod);
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
return results.success.push(validationMethod);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
component.setState({
|
||||
_isValid: isValid,
|
||||
_serverError: null
|
||||
}, this.validateForm);
|
||||
return results;
|
||||
|
||||
},
|
||||
|
||||
// Validate the form by going through all child input components
|
||||
// and check their state
|
||||
validateForm: function () {
|
||||
var allIsValid = true;
|
||||
var inputs = this.inputs;
|
||||
|
||||
Object.keys(inputs).forEach(function (name) {
|
||||
if (!inputs[name].state._isValid) {
|
||||
allIsValid = false;
|
||||
|
||||
// We need a callback as we are validating all inputs again. This will
|
||||
// run when the last component has set its state
|
||||
var onValidationComplete = function () {
|
||||
var allIsValid = this.inputs.every(component => {
|
||||
return component.state._isValid;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
|
||||
if (allIsValid) {
|
||||
this.props.onValid();
|
||||
} else {
|
||||
this.props.onInvalid();
|
||||
}
|
||||
|
||||
// Tell the form that it can start to trigger change events
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
|
||||
}.bind(this);
|
||||
|
||||
// Run validation again in case affected by other inputs. The
|
||||
// last component validated will run the onValidationComplete callback
|
||||
this.inputs.forEach((component, index) => {
|
||||
var validation = this.runValidation(component);
|
||||
if (validation.isValid && component.state._externalError) {
|
||||
validation.isValid = false;
|
||||
}
|
||||
component.setState({
|
||||
_isValid: validation.isValid,
|
||||
_isRequired: validation.isRequired,
|
||||
_validationError: validation.error,
|
||||
_externalError: !validation.isValid && component.state._externalError ? component.state._externalError : null
|
||||
}, index === this.inputs.length - 1 ? onValidationComplete : null);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isValid: allIsValid
|
||||
});
|
||||
// If there are no inputs, set state where form is ready to trigger
|
||||
// change event. New inputs might be added later
|
||||
if (!this.inputs.length) {
|
||||
this.setState({
|
||||
canChange: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Method put on each input component to register
|
||||
// itself to the form
|
||||
attachToForm: function (component) {
|
||||
this.inputs[component.props.name] = component;
|
||||
this.model[component.props.name] = component.state._value;
|
||||
|
||||
if (this.inputs.indexOf(component) === -1) {
|
||||
this.inputs.push(component);
|
||||
}
|
||||
|
||||
this.validate(component);
|
||||
},
|
||||
|
||||
// Method put on each input component to unregister
|
||||
// itself from the form
|
||||
detachFromForm: function (component) {
|
||||
delete this.inputs[component.props.name];
|
||||
delete this.model[component.props.name];
|
||||
var componentPos = this.inputs.indexOf(component);
|
||||
|
||||
if (componentPos !== -1) {
|
||||
this.inputs = this.inputs.slice(0, componentPos)
|
||||
.concat(this.inputs.slice(componentPos + 1));
|
||||
}
|
||||
|
||||
this.validateForm();
|
||||
},
|
||||
render: function () {
|
||||
var submitButton = React.DOM.button({
|
||||
className: this.props.submitButtonClass || options.submitButtonClass,
|
||||
disabled: this.state.isSubmitting || !this.state.isValid
|
||||
}, this.props.submitLabel || 'Submit');
|
||||
var {
|
||||
mapping,
|
||||
validationErrors,
|
||||
onSubmit,
|
||||
onValid,
|
||||
onValidSubmit,
|
||||
onInvalid,
|
||||
onInvalidSubmit,
|
||||
onChange,
|
||||
reset,
|
||||
preventExternalInvalidation,
|
||||
onSuccess,
|
||||
onError,
|
||||
...nonFormsyProps
|
||||
} = this.props;
|
||||
|
||||
var cancelButton = React.DOM.button({
|
||||
onClick: this.props.onCancel,
|
||||
disabled: this.state.isSubmitting,
|
||||
className: this.props.cancelButtonClass || options.cancelButtonClass
|
||||
}, this.props.cancelLabel || 'Cancel');
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children,
|
||||
React.DOM.div({
|
||||
className: this.props.buttonWrapperClass || options.buttonWrapperClass
|
||||
},
|
||||
this.props.onCancel ? cancelButton : null,
|
||||
this.props.hideSubmit || options.hideSubmit ? null : submitButton
|
||||
)
|
||||
return (
|
||||
<form {...nonFormsyProps} onSubmit={this.submit}>
|
||||
{this.props.children}
|
||||
</form>
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -350,4 +454,4 @@ if (!global.exports && !global.module && (!global.define || !global.define.amd))
|
|||
global.Formsy = Formsy;
|
||||
}
|
||||
|
||||
module.exports = Formsy;
|
||||
module.exports = Formsy;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
module.exports = {
|
||||
arraysDiffer: function (a, b) {
|
||||
var isDifferent = false;
|
||||
if (a.length !== b.length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
a.forEach(function (item, index) {
|
||||
if (!this.isSame(item, b[index])) {
|
||||
isDifferent = true;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
return isDifferent;
|
||||
},
|
||||
|
||||
objectsDiffer: function (a, b) {
|
||||
var isDifferent = false;
|
||||
if (Object.keys(a).length !== Object.keys(b).length) {
|
||||
isDifferent = true;
|
||||
} else {
|
||||
Object.keys(a).forEach(function (key) {
|
||||
if (!this.isSame(a[key], b[key])) {
|
||||
isDifferent = true;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
return isDifferent;
|
||||
},
|
||||
|
||||
isSame: function (a, b) {
|
||||
if (typeof a !== typeof b) {
|
||||
return false;
|
||||
} else if (Array.isArray(a) && Array.isArray(b)) {
|
||||
return !this.arraysDiffer(a, b);
|
||||
} else if (typeof a === 'function') {
|
||||
return a.toString() === b.toString();
|
||||
} else if (typeof a === 'object' && a !== null && b !== null) {
|
||||
return !this.objectsDiffer(a, b);
|
||||
}
|
||||
|
||||
return a === b;
|
||||
},
|
||||
|
||||
find: function (collection, fn) {
|
||||
for (var i = 0, l = collection.length; i < l; i++) {
|
||||
var item = collection[i];
|
||||
if (fn(item)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
var isExisty = function (value) {
|
||||
return value !== null && value !== undefined;
|
||||
};
|
||||
|
||||
var isEmpty = function (value) {
|
||||
return value === '';
|
||||
};
|
||||
|
||||
var validations = {
|
||||
isDefaultRequiredValue: function (values, value) {
|
||||
return value === undefined || value === '';
|
||||
},
|
||||
isExisty: function (values, value) {
|
||||
return isExisty(value);
|
||||
},
|
||||
matchRegexp: function (values, value, regexp) {
|
||||
return !isExisty(value) || isEmpty(value) || regexp.test(value);
|
||||
},
|
||||
isUndefined: function (values, value) {
|
||||
return value === undefined;
|
||||
},
|
||||
isEmptyString: function (values, value) {
|
||||
return isEmpty(value);
|
||||
},
|
||||
isEmail: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i);
|
||||
},
|
||||
isUrl: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i);
|
||||
},
|
||||
isTrue: function (values, value) {
|
||||
return value === true;
|
||||
},
|
||||
isFalse: function (values, value) {
|
||||
return value === false;
|
||||
},
|
||||
isNumeric: function (values, value) {
|
||||
if (typeof value === 'number') {
|
||||
return true;
|
||||
}
|
||||
return validations.matchRegexp(values, value, /^[-+]?(?:\d*[.])?\d+$/);
|
||||
},
|
||||
isAlpha: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z]+$/i);
|
||||
},
|
||||
isAlphanumeric: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^[0-9A-Z]+$/i);
|
||||
},
|
||||
isInt: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:[-+]?(?:0|[1-9]\d*))$/);
|
||||
},
|
||||
isFloat: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^(?:[-+]?(?:\d+))?(?:\.\d*)?(?:[eE][\+\-]?(?:\d+))?$/);
|
||||
},
|
||||
isWords: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z\s]+$/i);
|
||||
},
|
||||
isSpecialWords: function (values, value) {
|
||||
return validations.matchRegexp(values, value, /^[A-Z\s\u00C0-\u017F]+$/i);
|
||||
},
|
||||
isLength: function (values, value, length) {
|
||||
return !isExisty(value) || isEmpty(value) || value.length === length;
|
||||
},
|
||||
equals: function (values, value, eql) {
|
||||
return !isExisty(value) || isEmpty(value) || value == eql;
|
||||
},
|
||||
equalsField: function (values, value, field) {
|
||||
return value == values[field];
|
||||
},
|
||||
maxLength: function (values, value, length) {
|
||||
return !isExisty(value) || value.length <= length;
|
||||
},
|
||||
minLength: function (values, value, length) {
|
||||
return !isExisty(value) || isEmpty(value) || value.length >= length;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = validations;
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import path from 'path';
|
||||
import testrunner from 'nodeunit/lib/reporters/default.js';
|
||||
import {jsdom} from 'jsdom';
|
||||
|
||||
global.document = jsdom();
|
||||
global.window = document.defaultView;
|
||||
global.navigator = global.window.navigator;
|
||||
|
||||
testrunner.run(['tests'], {
|
||||
"error_prefix": "\u001B[31m",
|
||||
"error_suffix": "\u001B[39m",
|
||||
"ok_prefix": "\u001B[32m",
|
||||
"ok_suffix": "\u001B[39m",
|
||||
"bold_prefix": "\u001B[1m",
|
||||
"bold_suffix": "\u001B[22m",
|
||||
"assertion_prefix": "\u001B[35m",
|
||||
"assertion_suffix": "\u001B[39m"
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,568 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import Formsy from './..';
|
||||
import TestInput, { InputFactory } from './utils/TestInput';
|
||||
import immediate from './utils/immediate';
|
||||
|
||||
export default {
|
||||
|
||||
'should return passed and setValue() value when using getValue()': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
test.equal(input.value, 'foo');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'foobar'}});
|
||||
test.equal(input.value, 'foobar');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should set back to pristine value when running reset': function (test) {
|
||||
|
||||
let reset = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
reset = this.resetValue;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Input name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'foobar'}});
|
||||
reset();
|
||||
test.equal(input.value, 'foo');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should return error message passed when calling getErrorMessage()': function (test) {
|
||||
|
||||
let getErrorMessage = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
getErrorMessage = this.getErrorMessage;
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Input name="foo" value="foo" validations="isEmail" validationError="Has to be email"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(getErrorMessage(), 'Has to be email');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should return true or false when calling isValid() depending on valid state': function (test) {
|
||||
|
||||
let isValid = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isValid = this.isValid;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Input name="foo" value="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(isValid(), false);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'foo@foo.com'}});
|
||||
test.equal(isValid(), true);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should return true or false when calling isRequired() depending on passed required attribute': function (test) {
|
||||
|
||||
const isRequireds = [];
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isRequireds.push(this.isRequired);
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Input name="foo" value=""/>
|
||||
<Input name="foo" value="" required/>
|
||||
<Input name="foo" value="foo" required="isLength:3"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(isRequireds[0](), false);
|
||||
test.equal(isRequireds[1](), true);
|
||||
test.equal(isRequireds[2](), true);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should return true or false when calling showRequired() depending on input being empty and required is passed, or not': function (test) {
|
||||
|
||||
const showRequireds = [];
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
showRequireds.push(this.showRequired);
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Input name="A" value="foo"/>
|
||||
<Input name="B" value="" required/>
|
||||
<Input name="C" value=""/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(showRequireds[0](), false);
|
||||
test.equal(showRequireds[1](), true);
|
||||
test.equal(showRequireds[2](), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should return true or false when calling isPristine() depending on input has been "touched" or not': function (test) {
|
||||
|
||||
let isPristine = null;
|
||||
const Input = InputFactory({
|
||||
componentDidMount() {
|
||||
isPristine = this.isPristine;
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form url="/users">
|
||||
<Input name="A" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(isPristine(), true);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'foo'}});
|
||||
test.equal(isPristine(), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow an undefined value to be updated to a value': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {value: undefined};
|
||||
},
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: 'foo'
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form url="/users">
|
||||
<TestInput name="A" value={this.state.value}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
form.changeValue();
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
immediate(() => {
|
||||
test.equal(input.value, 'foo');
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should be able to test a values validity': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isValidValue('foo@bar.com'), true);
|
||||
test.equal(input.isValidValue('foo@bar'), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to use an object as validations property': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isValidValue('foo@bar.com'), true);
|
||||
test.equal(input.isValidValue('foo@bar'), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to pass complex values to a validation rule': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
matchRegexp: /foo/
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input, {target: {value: 'bar'}});
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to run a function to validate': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
customValidationA(values, value) {
|
||||
return value === 'foo';
|
||||
},
|
||||
customValidationB(values, value) {
|
||||
return value === 'foo' && values.A === 'foo';
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A" validations={{
|
||||
custom: this.customValidationA
|
||||
}} value="foo"/>
|
||||
<TestInput name="B" validations={{
|
||||
custom: this.customValidationB
|
||||
}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
test.equal(inputComponent[0].isValid(), true);
|
||||
test.equal(inputComponent[1].isValid(), true);
|
||||
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT');
|
||||
TestUtils.Simulate.change(input[0], {target: {value: 'bar'}});
|
||||
test.equal(inputComponent[0].isValid(), false);
|
||||
test.equal(inputComponent[1].isValid(), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should not override error messages with error messages passed by form if passed eror messages is an empty object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form validationErrors={{}}>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar3');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
|
||||
'should override all error messages with error messages passed by form': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form validationErrors={{A: 'bar'}}>
|
||||
<TestInput name="A" validations={{
|
||||
isEmail: true
|
||||
}} validationError="bar2" validationErrors={{isEmail: 'bar3'}} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should override validation rules with required rules': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
}}
|
||||
validationError="bar"
|
||||
validationErrors={{isEmail: 'bar2', isLength: 'bar3'}}
|
||||
value="f"
|
||||
required={{
|
||||
isLength: 1
|
||||
}}
|
||||
/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar3');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fall back to default error message when non exist in validationErrors map': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A"
|
||||
validations={{
|
||||
isEmail: true
|
||||
}}
|
||||
validationError="bar"
|
||||
validationErrors={{foo: 'bar'}}
|
||||
value="foo"
|
||||
/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.getErrorMessage(), 'bar');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should not be valid if it is required and required rule is true': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="A"
|
||||
required
|
||||
/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should handle objects and arrays as values': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
foo: {foo: 'bar'},
|
||||
bar: ['foo']
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value={this.state.foo}/>
|
||||
<TestInput name="bar" value={this.state.bar}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
form.setState({
|
||||
foo: {foo: 'foo'},
|
||||
bar: ['bar']
|
||||
});
|
||||
|
||||
const inputs = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
test.deepEqual(inputs[0].getValue(), {foo: 'foo'});
|
||||
test.deepEqual(inputs[1].getValue(), ['bar']);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should handle isFormDisabled with dynamic inputs': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
bool: true
|
||||
};
|
||||
},
|
||||
flip() {
|
||||
this.setState({
|
||||
bool: !this.state.bool
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form disabled={this.state.bool}>
|
||||
{this.state.bool ?
|
||||
<TestInput name="foo" /> :
|
||||
<TestInput name="bar" />
|
||||
}
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isFormDisabled(), true);
|
||||
form.flip();
|
||||
test.equal(input.isFormDisabled(), false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow for dot notation in name which maps to a deep object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
onSubmit(model) {
|
||||
test.deepEqual(model, {foo: {bar: 'foo', test: 'test'}});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
<TestInput name="foo.bar" value="foo"/>
|
||||
<TestInput name="foo.test" value="test"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.expect(1);
|
||||
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow for application/x-www-form-urlencoded syntax and convert to object': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
onSubmit(model) {
|
||||
test.deepEqual(model, {foo: ['foo', 'bar']});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={this.onSubmit}>
|
||||
<TestInput name="foo[0]" value="foo"/>
|
||||
<TestInput name="foo[1]" value="bar"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.expect(1);
|
||||
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'input should rendered once with PureRenderMixin': function (test) {
|
||||
|
||||
var renderSpy = sinon.spy();
|
||||
|
||||
const Input = InputFactory({
|
||||
mixins: [Formsy.Mixin, PureRenderMixin],
|
||||
render() {
|
||||
renderSpy();
|
||||
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<Input name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(renderSpy.calledOnce, true);
|
||||
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,748 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import TestInput from './utils/TestInput';
|
||||
import TestInputHoc from './utils/TestInputHoc';
|
||||
import immediate from './utils/immediate';
|
||||
import sinon from 'sinon';
|
||||
|
||||
export default {
|
||||
|
||||
'Setting up a form': {
|
||||
'should expose the users DOM node through an innerRef prop': function (test) {
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInputHoc name="name" innerRef={(c) => { this.name = c; }} />
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = form.name;
|
||||
test.equal(input.methodOnWrappedInstance('foo'), 'foo');
|
||||
|
||||
test.done();
|
||||
},
|
||||
|
||||
'should render a form into the document': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<Formsy.Form></Formsy.Form>);
|
||||
test.equal(ReactDOM.findDOMNode(form).tagName, 'FORM');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should set a class name if passed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument( <Formsy.Form className="foo"></Formsy.Form>);
|
||||
test.equal(ReactDOM.findDOMNode(form).className, 'foo');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow for null/undefined children': function (test) {
|
||||
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
<h1>Test</h1>
|
||||
{ null }
|
||||
{ undefined }
|
||||
<TestInput name="name" value={ 'foo' } />
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
immediate(() => {
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.deepEqual(model, {name: 'foo'});
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should allow for inputs being added dynamically': function (test) {
|
||||
|
||||
const inputs = [];
|
||||
let forceUpdate = null;
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait before adding the input
|
||||
setTimeout(() => {
|
||||
inputs.push(<TestInput name="test" value="" key={inputs.length}/>);
|
||||
|
||||
forceUpdate(() => {
|
||||
// Wait for next event loop, as that does the form
|
||||
immediate(() => {
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.ok('test' in model);
|
||||
test.done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}, 10);
|
||||
|
||||
},
|
||||
|
||||
'should allow dynamically added inputs to update the form-model': function (test) {
|
||||
|
||||
const inputs = [];
|
||||
let forceUpdate = null;
|
||||
let model = null;
|
||||
const TestForm = React.createClass({
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
{inputs}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait before adding the input
|
||||
immediate(() => {
|
||||
inputs.push(<TestInput name="test" key={inputs.length}/>);
|
||||
|
||||
forceUpdate(() => {
|
||||
|
||||
// Wait for next event loop, as that does the form
|
||||
immediate(() => {
|
||||
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'foo'}});
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.equal(model.test, 'foo');
|
||||
test.done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should allow a dynamically updated input to update the form-model': function (test) {
|
||||
|
||||
let forceUpdate = null;
|
||||
let model = null;
|
||||
|
||||
const TestForm = React.createClass({
|
||||
componentWillMount() {
|
||||
forceUpdate = this.forceUpdate.bind(this);
|
||||
},
|
||||
render() {
|
||||
const input = <TestInput name="test" value={this.props.value} />;
|
||||
|
||||
return (
|
||||
<Formsy.Form onSubmit={(formModel) => (model = formModel)}>
|
||||
{input}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
let form = TestUtils.renderIntoDocument(<TestForm value="foo"/>);
|
||||
|
||||
// Wait before changing the input
|
||||
immediate(() => {
|
||||
form = TestUtils.renderIntoDocument(<TestForm value="bar"/>);
|
||||
|
||||
forceUpdate(() => {
|
||||
// Wait for next event loop, as that does the form
|
||||
immediate(() => {
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.equal(model.test, 'bar');
|
||||
test.done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'validations': {
|
||||
|
||||
'should run when the input changes': function (test) {
|
||||
|
||||
const runRule = sinon.spy();
|
||||
const notRunRule = sinon.spy();
|
||||
|
||||
Formsy.addValidationRule('runRule', runRule);
|
||||
Formsy.addValidationRule('notRunRule', notRunRule);
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<TestInput name="one" validations="runRule" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
test.equal(runRule.calledWith({one: 'bar'}, 'bar', true), true);
|
||||
test.equal(notRunRule.called, false);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow the validation to be changed': function (test) {
|
||||
|
||||
const ruleA = sinon.spy();
|
||||
const ruleB = sinon.spy();
|
||||
Formsy.addValidationRule('ruleA', ruleA);
|
||||
Formsy.addValidationRule('ruleB', ruleB);
|
||||
|
||||
class TestForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {rule: 'ruleA'};
|
||||
}
|
||||
changeRule() {
|
||||
this.setState({
|
||||
rule: 'ruleB'
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="one" validations={this.state.rule} value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
form.changeRule();
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
test.equal(ruleB.calledWith({one: 'bar'}, 'bar', true), true);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should invalidate a form if dynamically inserted input is invalid': function (test) {
|
||||
|
||||
const isInValidSpy = sinon.spy();
|
||||
|
||||
class TestForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {showSecondInput: false};
|
||||
}
|
||||
addInput() {
|
||||
this.setState({
|
||||
showSecondInput: true
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="formsy" onInvalid={isInValidSpy}>
|
||||
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
|
||||
{
|
||||
this.state.showSecondInput ?
|
||||
<TestInput name="two" validations="isEmail" value="foo@bar"/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.equal(form.refs.formsy.state.isValid, true);
|
||||
form.addInput();
|
||||
|
||||
immediate(() => {
|
||||
test.equal(isInValidSpy.called, true);
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should validate a form when removing an invalid input': function (test) {
|
||||
|
||||
const isValidSpy = sinon.spy();
|
||||
|
||||
class TestForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {showSecondInput: true};
|
||||
}
|
||||
removeInput() {
|
||||
this.setState({
|
||||
showSecondInput: false
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form ref="formsy" onValid={isValidSpy}>
|
||||
<TestInput name="one" validations="isEmail" value="foo@bar.com"/>
|
||||
{
|
||||
this.state.showSecondInput ?
|
||||
<TestInput name="two" validations="isEmail" value="foo@bar"/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
test.equal(form.refs.formsy.state.isValid, false);
|
||||
form.removeInput();
|
||||
|
||||
immediate(() => {
|
||||
test.equal(isValidSpy.called, true);
|
||||
test.done();
|
||||
});
|
||||
|
||||
|
||||
},
|
||||
|
||||
'runs multiple validations': function (test) {
|
||||
|
||||
const ruleA = sinon.spy();
|
||||
const ruleB = sinon.spy();
|
||||
Formsy.addValidationRule('ruleA', ruleA);
|
||||
Formsy.addValidationRule('ruleB', ruleB);
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<TestInput name="one" validations="ruleA,ruleB" value="foo" />
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
test.equal(ruleA.calledWith({one: 'bar'}, 'bar', true), true);
|
||||
test.equal(ruleB.calledWith({one: 'bar'}, 'bar', true), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'should not trigger onChange when form is mounted': function (test) {
|
||||
|
||||
|
||||
const hasChanged = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return <Formsy.Form onChange={hasChanged}></Formsy.Form>;
|
||||
}
|
||||
});
|
||||
TestUtils.renderIntoDocument(<TestForm/>);
|
||||
test.equal(hasChanged.called, false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should trigger onChange once when form element is changed': function (test) {
|
||||
|
||||
const hasChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasChanged}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT'), {target: {value: 'bar'}});
|
||||
test.equal(hasChanged.calledOnce, true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should trigger onChange once when new input is added to form': function (test) {
|
||||
|
||||
const hasChanged = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
showInput: false
|
||||
};
|
||||
},
|
||||
addInput() {
|
||||
this.setState({
|
||||
showInput: true
|
||||
})
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onChange={hasChanged}>
|
||||
{
|
||||
this.state.showInput ?
|
||||
<TestInput name="test"/>
|
||||
:
|
||||
null
|
||||
}
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
form.addInput();
|
||||
immediate(() => {
|
||||
test.equal(hasChanged.calledOnce, true);
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'Update a form': {
|
||||
|
||||
'should allow elements to check if the form is disabled': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() { return { disabled: true }; },
|
||||
enableForm() { this.setState({ disabled: false }); },
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form disabled={this.state.disabled}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isFormDisabled(), true);
|
||||
|
||||
form.enableForm();
|
||||
immediate(() => {
|
||||
test.equal(input.isFormDisabled(), false);
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should be possible to pass error state of elements by changing an errors attribute': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() { return { validationErrors: { foo: 'bar' } }; },
|
||||
onChange(values) {
|
||||
this.setState(values.foo ? { validationErrors: {} } : { validationErrors: {foo: 'bar'} });
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onChange={this.onChange} validationErrors={this.state.validationErrors}>
|
||||
<TestInput name="foo"/>
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
// Wait for update
|
||||
immediate(() => {
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.getErrorMessage(), 'bar');
|
||||
input.setValue('gotValue');
|
||||
|
||||
// Wait for update
|
||||
immediate(() => {
|
||||
test.equal(input.getErrorMessage(), null);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should trigger an onValidSubmit when submitting a valid form': function (test) {
|
||||
|
||||
let isCalled = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onValidSubmit={isCalled}>
|
||||
<TestInput name="foo" validations="isEmail" value="foo@bar.com"/>
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
|
||||
test.equal(isCalled.called,true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should trigger an onInvalidSubmit when submitting an invalid form': function (test) {
|
||||
|
||||
let isCalled = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onInvalidSubmit={isCalled}>
|
||||
<TestInput name="foo" validations="isEmail" value="foo@bar"/>
|
||||
</Formsy.Form>);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const FoundForm = TestUtils.findRenderedComponentWithType(form, TestForm);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(FoundForm));
|
||||
test.equal(isCalled.called, true);
|
||||
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'value === false': {
|
||||
|
||||
'should call onSubmit correctly': function (test) {
|
||||
|
||||
const onSubmit = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={onSubmit}>
|
||||
<TestInput name="foo" value={false} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.equal(onSubmit.calledWith({foo: false}), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should allow dynamic changes to false': function (test) {
|
||||
|
||||
const onSubmit = sinon.spy();
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={onSubmit}>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
form.changeValue();
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.equal(onSubmit.calledWith({foo: false}), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should say the form is submitted': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value={true} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(input.isFormSubmitted(), false);
|
||||
TestUtils.Simulate.submit(ReactDOM.findDOMNode(form));
|
||||
test.equal(input.isFormSubmitted(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to reset the form to its pristine state': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
test.equal(input.getValue(), true);
|
||||
form.changeValue();
|
||||
test.equal(input.getValue(), false);
|
||||
formsyForm.reset();
|
||||
test.equal(input.getValue(), true);
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to reset the form using custom data': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
value: true
|
||||
};
|
||||
},
|
||||
changeValue() {
|
||||
this.setState({
|
||||
value: false
|
||||
});
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value={this.state.value} type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
|
||||
test.equal(input.getValue(), true);
|
||||
form.changeValue();
|
||||
test.equal(input.getValue(), false);
|
||||
formsyForm.reset({
|
||||
foo: 'bar'
|
||||
});
|
||||
test.equal(input.getValue(), 'bar');
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'should be able to reset the form to empty values': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" value="42" type="checkbox" />
|
||||
<button type="submit">Save</button>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
const formsyForm = TestUtils.findRenderedComponentWithType(form, Formsy.Form);
|
||||
|
||||
formsyForm.reset({
|
||||
foo: ''
|
||||
});
|
||||
test.equal(input.getValue(), '');
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'.isChanged()': {
|
||||
|
||||
'initially returns false': function (test) {
|
||||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
);
|
||||
test.equal(form.isChanged(), false);
|
||||
test.equal(hasOnChanged.called, false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'returns true when changed': function (test) {
|
||||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
test.equal(form.isChanged(), true);
|
||||
test.equal(hasOnChanged.calledWith({one: 'bar'}), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'returns false if changes are undone': function (test) {
|
||||
|
||||
const hasOnChanged = sinon.spy();
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onChange={hasOnChanged}>
|
||||
<TestInput name="one" value="foo" />
|
||||
</Formsy.Form>
|
||||
);
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'input');
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'bar'}});
|
||||
test.equal(hasOnChanged.calledWith({one: 'bar'}, true), true);
|
||||
|
||||
TestUtils.Simulate.change(ReactDOM.findDOMNode(input), {target: {value: 'foo'}});
|
||||
test.equal(form.isChanged(), false);
|
||||
test.equal(hasOnChanged.calledWith({one: 'foo'}, false), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
import immediate from './utils/immediate';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="equals:foo" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass when the value is equal': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail when the value is not equal': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="fo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isAlpha" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a string is only latin letters': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string with numbers': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isAlphanumeric" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a string is only latin letters': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string with numbers': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="myValue42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a non alpha and number symbols': function (test) {
|
||||
|
||||
const value = '!@#$%^&*()';
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={value}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isEmail" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with "foo"': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with "foo@foo.com"': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo@foo.com"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={''}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isEmptyString" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with non-empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a zero': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isExisty" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a zero': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isFloat" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a number as string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail string with digits': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an int': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a float': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a float in science notation': function (test) {
|
||||
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a zero': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isInt" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="abc"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a number as string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail string with digits': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an int': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a float': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a float in science notation': function (test) {
|
||||
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a zero': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'isLength:3': {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string too small': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="hi"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string too long': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="hi ho happ"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with matching length': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue="foo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:3" inputValue={123}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'isLength:0': {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string too small': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue="hi"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string too long': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue="hi ho happ"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with matching length': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="isLength:0" inputValue={123}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isNumeric" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with an unempty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a number as string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="+42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number as string with not digits': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="42 is an answer"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an int': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a float': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={Math.PI}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a float in science notation': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="-1e3"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a zero': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={0}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isUrl" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with "foo"': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foo"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with "https://www.google.com/"': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="https://www.google.com/"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="isWords" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a 1 word': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with 2 words': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup dude"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a string with numbers': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="sup 42"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations="maxLength:3" value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass when a string\'s length is smaller': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="hi"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass when a string\'s length is equal': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="bar"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail when a string\'s length is bigger': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue="foobar"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import { InputFactory } from './utils/TestInput';
|
||||
|
||||
const TestInput = InputFactory({
|
||||
render() {
|
||||
return <input value={this.getValue()} readOnly/>;
|
||||
}
|
||||
});
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form>
|
||||
<TestInput name="foo" validations={this.props.rule} value={this.props.inputValue}/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default {
|
||||
|
||||
'minLength:3': {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass when a string\'s length is bigger': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue="myValue"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail when a string\'s length is smaller': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue="my"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:3" inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
'minLength:0': {
|
||||
|
||||
'should pass with a default value': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass when a string\'s length is bigger': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue="myValue"/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with empty string': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue=""/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with an undefined': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={undefined}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should pass with a null': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={null}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should fail with a number': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm rule="minLength:0" inputValue={42}/>);
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import utils from './../src/utils.js';
|
||||
|
||||
export default {
|
||||
|
||||
'should check equality of objects and arrays': function (test) {
|
||||
|
||||
const objA = { foo: 'bar' };
|
||||
const objB = { foo: 'bar' };
|
||||
const objC = [{ foo: ['bar'] }];
|
||||
const objD = [{ foo: ['bar'] }];
|
||||
const objE = undefined;
|
||||
const objF = undefined;
|
||||
const objG = null;
|
||||
const objH = null;
|
||||
|
||||
test.equal(utils.isSame(objA, objB), true);
|
||||
test.equal(utils.isSame(objC, objD), true);
|
||||
test.equal(utils.isSame(objA, objD), false);
|
||||
|
||||
test.equal(utils.isSame(objE, objF), true);
|
||||
test.equal(utils.isSame(objA, objF), false);
|
||||
test.equal(utils.isSame(objE, objA), false);
|
||||
|
||||
test.equal(utils.isSame(objG, objH), true);
|
||||
test.equal(utils.isSame(objA, objH), false);
|
||||
test.equal(utils.isSame(objC, objH), false);
|
||||
test.equal(utils.isSame(objG, objA), false);
|
||||
|
||||
test.equal(utils.isSame(() => {}, () => {}), true);
|
||||
test.equal(utils.isSame(objA, () => {}), false);
|
||||
test.equal(utils.isSame(() => {}, objA), false);
|
||||
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
import React from 'react';
|
||||
import TestUtils from 'react-addons-test-utils';
|
||||
|
||||
import Formsy from './..';
|
||||
import TestInput, {InputFactory} from './utils/TestInput';
|
||||
import immediate from './utils/immediate';
|
||||
import sinon from 'sinon';
|
||||
|
||||
export default {
|
||||
|
||||
'should reset only changed form element when external error is passed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar', bar: 'foo' })}>
|
||||
<TestInput name="foo"/>
|
||||
<TestInput name="bar"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.scryRenderedDOMComponentsWithTag(form, 'INPUT')[0];
|
||||
const inputComponents = TestUtils.scryRenderedComponentsWithType(form, TestInput);
|
||||
|
||||
form.submit();
|
||||
test.equal(inputComponents[0].isValid(), false);
|
||||
test.equal(inputComponents[1].isValid(), false);
|
||||
|
||||
TestUtils.Simulate.change(input, {target: {value: 'bar'}});
|
||||
immediate(() => {
|
||||
test.equal(inputComponents[0].isValid(), true);
|
||||
test.equal(inputComponents[1].isValid(), false);
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should let normal validation take over when component with external error is changed': function (test) {
|
||||
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
const inputComponent = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
|
||||
form.submit();
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
|
||||
TestUtils.Simulate.change(input, {target: {value: 'bar'}});
|
||||
immediate(() => {
|
||||
test.equal(inputComponent.getValue(), 'bar');
|
||||
test.equal(inputComponent.isValid(), false);
|
||||
test.done();
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
'should trigger an onValid handler, if passed, when form is valid': function (test) {
|
||||
|
||||
const onValid = sinon.spy();
|
||||
const onInvalid = sinon.spy();
|
||||
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
|
||||
<TestInput name="foo" value="bar" required/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(onValid.called, true);
|
||||
test.equal(onInvalid.called, false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should trigger an onInvalid handler, if passed, when form is invalid': function (test) {
|
||||
|
||||
const onValid = sinon.spy();
|
||||
const onInvalid = sinon.spy();
|
||||
|
||||
TestUtils.renderIntoDocument(
|
||||
<Formsy.Form onValid={onValid} onInvalid={onInvalid}>
|
||||
<TestInput name="foo" required />
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
test.equal(onValid.called, false);
|
||||
test.equal(onInvalid.called, true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should be able to use provided validate function': function (test) {
|
||||
|
||||
let isValid = false;
|
||||
const CustomInput = InputFactory({
|
||||
componentDidMount() {
|
||||
isValid = this.isValid();
|
||||
}
|
||||
});
|
||||
const form = TestUtils.renderIntoDocument(
|
||||
<Formsy.Form>
|
||||
<CustomInput name="foo" value="foo" required/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
|
||||
const input = TestUtils.findRenderedDOMComponentWithTag(form, 'INPUT');
|
||||
test.equal(isValid, true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should provide invalidate callback on onValiSubmit': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onValidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), false);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should provide invalidate callback on onInvalidSubmit': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onInvalidSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo" validations="isEmail"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.getErrorMessage(), 'bar');
|
||||
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should not invalidate inputs on external errors with preventExternalInvalidation prop': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form
|
||||
preventExternalInvalidation
|
||||
onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), true);
|
||||
test.done();
|
||||
|
||||
},
|
||||
|
||||
'should invalidate inputs on external errors without preventExternalInvalidation prop': function (test) {
|
||||
|
||||
const TestForm = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<Formsy.Form onSubmit={(model, reset, invalidate) => invalidate({ foo: 'bar' })}>
|
||||
<TestInput name="foo" value="foo"/>
|
||||
</Formsy.Form>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const form = TestUtils.renderIntoDocument(<TestForm/>);
|
||||
const formEl = TestUtils.findRenderedDOMComponentWithTag(form, 'form');
|
||||
const input = TestUtils.findRenderedComponentWithType(form, TestInput);
|
||||
TestUtils.Simulate.submit(formEl);
|
||||
test.equal(input.isValid(), false);
|
||||
test.done();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import Formsy from './../..';
|
||||
|
||||
const defaultProps = {
|
||||
mixins: [Formsy.Mixin],
|
||||
getDefaultProps() {
|
||||
return { type: 'text' };
|
||||
},
|
||||
updateValue(event) {
|
||||
this.setValue(event.target[this.props.type === 'checkbox' ? 'checked' : 'value']);
|
||||
},
|
||||
render() {
|
||||
return <input type={this.props.type} value={this.getValue()} onChange={this.updateValue}/>;
|
||||
}
|
||||
};
|
||||
|
||||
export function InputFactory(props) {
|
||||
return React.createClass(Object.assign(defaultProps, props));
|
||||
}
|
||||
|
||||
export default React.createClass(defaultProps);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import React from 'react';
|
||||
import { HOC as formsyHoc } from './../..';
|
||||
|
||||
const defaultProps = {
|
||||
methodOnWrappedInstance(param) {
|
||||
return param;
|
||||
},
|
||||
render() {
|
||||
return (<input />);
|
||||
},
|
||||
};
|
||||
|
||||
export default formsyHoc(React.createClass(defaultProps));
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default function (fn) {
|
||||
setTimeout(fn, 0);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
|
||||
devtool: 'source-map',
|
||||
entry: path.resolve(__dirname, 'src', 'main.js'),
|
||||
externals: 'react',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'release'),
|
||||
filename: 'formsy-react.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'Formsy'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel' },
|
||||
{ test: /\.json$/, loader: 'json' }
|
||||
]
|
||||
}
|
||||
|
||||
};
|
||||
Loading…
Reference in New Issue