Compare commits

...

309 Commits

Author SHA1 Message Date
Aesop Wolf ebd84bb026 Fix warning about isMounted being deprecated 2017-06-17 19:30:05 -07:00
Aesop Wolf 7dc68fc633 Migrate from React.createClass to create-react-class 2017-06-16 14:28:30 -07:00
Aesop Wolf a20da5b7cd Migrate from React.PropTypes to prop-types 2017-06-15 20:10:13 -07:00
Aesop Wolf c5dfa4b31a v0.19.2 2017-01-23 19:18:56 -08:00
Aesop Wolf f1ba9d417e Revert "fix: show the default validation message when there is a required error (#386)"
This reverts commit ac301f8950.
2017-01-23 19:13:05 -08:00
Aesop Wolf 57f97cb045 v0.19.1 2017-01-18 13:32:55 -08:00
CT Wu ac301f8950 fix: show the default validation message when there is a required error (#386)
* Show the default validation error message when required is invalid

* Fix showError
2017-01-18 13:31:02 -08:00
Tim Brayshaw d68586180a Remove stray sentence. (#415) 2017-01-12 09:37:57 -08:00
Aesop Wolf f395839e2c Update API.md
Add note about xregexp
2016-12-29 15:11:36 -08:00
David Blurton 73dd46b5e3 Fix crash when comparing array with null (#404) 2016-12-10 09:20:21 -08:00
Scott Silvi 84cdcb117c docs: Removing unnecessary single quote (#410) 2016-12-08 22:51:17 -08:00
Aesop Wolf c56e8c3328 0.19.0 2016-12-08 22:34:08 -08:00
Aesop Wolf 2b249113d8 feat: expose inner element when using HOC (#399)
Use `innerRef={(c) => { this.myInput = c; }}` on your HOC to access the internal element
2016-12-08 22:24:19 -08:00
Christian Alfoni 15493dce67 Update README.md 2016-10-05 16:04:08 +02:00
Christian Alfoni 89dbed0468 Update LICENSE 2016-10-05 14:46:46 +02:00
Dmitry Semigradsky 168521e578 Merge pull request #366 from maniart/patch-2
Nit-picky typo
2016-07-21 17:57:27 +03:00
Mani Nilchiani 42ba9616d2 Nit-picky typo
This is some *great* documentation work. I read it so carefully that this nit-picky typo came to my attention.
2016-07-13 12:57:29 -04:00
Semigradsky 6b167cbfe1 0.18.1 2016-07-05 11:52:41 +03:00
Semigradsky 24a6b6217d Fixed #335: getErrorMessages is undocumented in API 2016-07-05 11:49:29 +03:00
Semigradsky d6950885a1 Fixed #298: RangeError: Maximum call stack size exceeded with custom validator 2016-07-05 11:36:06 +03:00
Semigradsky 3cdcdf57ce Fixed uncontrollable/controllable components in examples 2016-07-05 11:22:25 +03:00
Semigradsky 1800442ea1 Fixed #361: Unknown props `onValidSubmit`, `onSubmitted` on <form> tag 2016-07-05 10:54:18 +03:00
Dmitry Semigradsky da8f041eca Merge pull request #357 from wizardzloy/patch-1
Use better displayNames for components wrapped into HOC
2016-07-05 10:41:16 +03:00
Vladimir Guguiev 9e923dd0dc Use better names for components wrapped into HOC
Currently the `displayName` of the component wrapped into HOC looks like `Constructor`, which isn't very descriptive.
This PR introduces a better `displayName` for such components: `Formsy(ComponentName)`
2016-06-11 13:21:44 +02:00
Semigradsky 89a2d4287b Update documentation 2016-04-21 13:16:00 +03:00
Semigradsky 6767a6b9eb Fix: Cannot reset value unless truthy 2016-04-21 13:05:47 +03:00
Semigradsky d84397906b 0.18.0 2016-04-19 11:12:31 +03:00
Semigradsky 112819f699 Update dependencies. Some minor changes. 2016-04-19 11:12:14 +03:00
Dmitry Semigradsky 4931256105 Fix link 2015-12-12 16:19:38 +03:00
Dmitry Semigradsky 7658d06cc8 Merge pull request #258 from sdemjanenko/fix_multiple_on_change
Fix multiple on change
2015-12-10 21:23:05 +03:00
Stephen Demjanenko 1df3e3520f Formsy.Form: omit Formsy-only props from the `<form/>` component 2015-12-10 09:38:01 -08:00
Stephen Demjanenko fec4576d1b FormsySpec: test that `onChange` is called only once 2015-12-10 09:37:57 -08:00
Dmitry Semigradsky b39fd2ed74 Fix link 2015-12-10 09:22:53 +03:00
Semigradsky 18467a94a1 Added `updateInputsWithError` to documentation. 2015-12-10 09:21:53 +03:00
Semigradsky d5268a4eef Added mapping to `getModel()` 2015-12-08 13:10:48 +03:00
Semigradsky 5b5bd0fb67 Add `getModel()` to documentation. 2015-12-08 12:00:49 +03:00
Dmitry Semigradsky 46c1f2c250 Merge pull request #275 from christianalfoni/issue-267
Issue 267
2015-12-08 11:39:23 +03:00
Semigradsky dcac495d79 Renamed `this.updateModel` to `this.getModel` 2015-12-07 13:56:47 +03:00
Semigradsky c4fa202ebf Fix `component[Will|Did]Update` behavior. Build release. 2015-12-02 16:50:06 +03:00
Semigradsky 7481b6da64 Fix #267 2015-12-02 16:39:39 +03:00
Semigradsky fbd09119d3 Added `dynamic form fields` example. 2015-12-01 17:00:43 +03:00
Dmitry Semigradsky f67f8317c9 Merge pull request #252 from neoziro/patch-2
Prevent multiple rendering with PureRenderMixin
2015-11-05 13:43:10 +03:00
Semigradsky 59bde8324c Added test for rendering elements with PureRenderMixin and Formsy.Mixin 2015-11-05 13:41:48 +03:00
Dmitry Semigradsky 8636cdabc3 Added link to codepen demo. 2015-11-03 15:02:53 +03:00
Dmitry Semigradsky cfebf17aea Merge pull request #242 from easyrider/master
Fix: Form.validationErrors disables input validation #239
2015-11-03 14:34:45 +03:00
Greg Bergé 6bcdb61b27 Prevent multiple rendering with PureRenderMixin
Creating a new instance of error triggers two additional rendering when typing in an input, using same instance prevent it to happen.
2015-10-28 12:48:46 +01:00
Dmitry Semigradsky 300a53f172 Merge pull request #251 from neoziro/patch-1
Remove babelify transform
2015-10-28 13:50:49 +03:00
Greg Bergé 36b5dd1dab Remove babelify transform
To be able to use this module in a browserify project without having to install babelify.
2015-10-28 11:23:15 +01:00
adam.dymowski 8cf8409e3a Fix: Form.validationErrors disables input validation #239 2015-10-20 09:19:24 +02:00
Dmitry Semigradsky e4d35f999b Merge pull request #243 from mbrookes/patch-2
Add formsy-material-ui to elements section.
2015-10-19 18:13:05 +03:00
Matt Brookes 78b2ada909 Add formsy-material-ui to elements section. 2015-10-19 15:52:20 +01:00
adam.dymowski 2a384f40a6 Fix: Form.validationErrors disables input validation #239 2015-10-19 16:19:03 +02:00
Dmitry Semigradsky b797979873 Update README.md 2015-10-15 11:52:10 +03:00
Dmitry Semigradsky 13615e82e4 Merge pull request #238 from mbrookes/patch-1
Add formsy-material-ui
2015-10-15 11:51:08 +03:00
Matt Brookes 2639319f9b Add formsy-material-ui
In alphabetical order. ;)
2015-10-14 22:01:08 +01:00
Christian Alfoni 2be44f1b32 React 0.14 support 2015-10-08 09:31:59 +02:00
Christian Alfoni 97923876d9 Merge pull request #233 from almasakchabayev/lib-main
Update package.json
2015-10-08 09:29:17 +02:00
Christian Alfoni 9fb4b93fec Merge pull request #229 from Mayank1791989/fix_missing_props_in_hoc
fix extending props in HOC.
2015-10-08 09:28:41 +02:00
Almas Akchabayev dd15a840a0 Update package.json 2015-10-08 09:29:39 +06:00
Mayank Agarwal 6f2a21e3ef fix extending props in HOC. 2015-10-06 11:25:59 +05:30
Christian Alfoni 5bfa7e53f1 Bumped version due to beta testing 2015-10-04 17:53:48 +02:00
Christian Alfoni 4ca7a57af7 New travis container 2015-10-04 17:49:19 +02:00
Christian Alfoni 3bd3ad699e Changed travis version 2015-10-04 17:45:18 +02:00
Christian Alfoni 19e28c6437 Set correct version release 2015-10-04 17:42:33 +02:00
Christian Alfoni 2846f0fadd Refactored tests and made React 0.14 friendly 2015-10-04 17:40:58 +02:00
Christian Alfoni 8254580e62 Merge pull request #221 from danpantry/react-0.14
Transpile with Babel prior to publishing to npm
2015-10-02 15:15:50 +02:00
Christian Alfoni 4670a9cc1f Merge pull request #214 from gusaiani/init-state-in-readme
Add getInitialState to README example
2015-10-02 14:56:27 +02:00
Christian Alfoni 2c6f7fe8f0 Merge pull request #213 from kryogenic/patch-1
make server validation more obvious in docs
2015-10-02 14:56:02 +02:00
Christian Alfoni 404f696bfb Merge pull request #211 from LestaD/patch-1
Add display name
2015-10-02 14:55:41 +02:00
Christian Alfoni e72f34a3d1 Merge pull request #210 from rozzzly/patch-1
fix links so they work on githib
2015-10-02 14:52:44 +02:00
Christian Alfoni 0c5e927f8b Merge pull request #205 from christianalfoni/isAlphanumeric
Added new validation rules: `isAlphanumeric`, `isInt`, `isFloat`
2015-10-02 14:42:46 +02:00
Christian Alfoni 43a8f66150 Merge pull request #197 from rblakeley/patch-1
fix #196
2015-10-02 13:46:25 +02:00
Christian Alfoni 2c4f73dd8e Merge pull request #195 from blittle/react-0.14
Pass through props inside the decorator
2015-10-02 13:38:18 +02:00
Dan Pantry 5083c31c8d transpile with Babel prior to publishing to npm
This enables Node environments to use require('formsy-react') instead of
having to transpile it themselves
2015-09-26 23:54:39 +01:00
Gustavo Saiani f559c8f9d4 Add getInitialState to README example 2015-09-21 17:14:15 -03:00
Kale bf77058a87 make server validation more obvious in docs 2015-09-21 07:17:27 -06:00
Sergey Sova 32aac21a4e Add display name 2015-09-18 15:01:07 +03:00
Patrick Lienau 349d5c7922 fix links so they work on githib
Before the links to the examples pointed towards `localhost:8080`; therefore not easily—well, it wasn't exactly hard to find either—accessible from the github repository. 'Tis but a simple fix, will probably save somebody else a few seconds. Great repo btw.
2015-09-17 08:37:19 -05:00
Christian Alfoni 523ab69a4e Update LICENSE 2015-09-12 17:14:58 +02:00
Christian Alfoni a8dd273148 Merge pull request #176 from silvenon/patch-3
Make it browserify-friendly
2015-09-12 16:37:10 +02:00
Christian Alfoni 3791b1976f Merge pull request #174 from silvenon/patch-1
Add example use case for require="isFalse"
2015-09-12 16:33:09 +02:00
Semigradsky a96f5246e7 Added new validation rules: `isAlphanumeric`, `isInt`, `isFloat` 2015-09-11 11:02:35 +03:00
Semigradsky 8fe0d4d73f Fixed React warnings 2015-09-10 16:10:17 +03:00
Semigradsky 5deb7554c9 Added description for `reset-values` example 2015-09-10 15:30:01 +03:00
Semigradsky 5c350d73f5 Merge branch 'react-0.14' of https://github.com/christianalfoni/formsy-react into react-0.14 2015-09-10 15:26:24 +03:00
Semigradsky 6a0d23cc03 Added `reset-values` example.
Moved examples to es2015
2015-09-10 15:26:17 +03:00
Dmitry Semigradsky d25a0ace2c Merge pull request #202 from zpalexander/examples-readme-broken-links
Fix 'localhost' links in 'Examples' readme
2015-09-10 13:27:47 +03:00
Dmitry Semigradsky e19a2eadbd Merge pull request #201 from iwangu/master
removed unneeded comparison
2015-09-10 13:24:06 +03:00
Zach Alexander 5c33b56e71 Fix 'localhost' links in 'Examples' readme 2015-09-09 10:44:51 -04:00
Semigradsky 7ab78f9603 Moved tests to es2015 2015-09-09 12:45:44 +03:00
Semigradsky cfb3a5af6f Removed unnecessary NodeJS versions 2015-09-09 11:03:19 +03:00
Dmitry Semigradsky 13688f1716 Update README.md 2015-09-09 10:52:00 +03:00
Ryan Blakeley 7f6ab4f52c add built files, fix #196 2015-09-04 10:48:06 -06:00
Ryan Blakeley 6b521d35e7 removed obsolete method call, fix #196 2015-09-04 10:35:47 -06:00
Ryan Blakeley c311e144fb fix #196 2015-09-04 10:27:14 -06:00
Bret Little b1275d813e Add built files 2015-09-03 18:16:16 -06:00
Bret Little 882eaa1801 Pass through props inside the decorator
This allows custom properties on inputs. Example:

<Input label="What is your first name?" name='firstName' required/>

The property label is now available within a custom input:

export default class Input extends React.Component {
	render() {
		return (
			<div className="form-group">
				<label>{this.props.label}</label>
				<input type="text" className="form-control"/>
			</div>
		)
	}
}
2015-09-01 18:00:06 -06:00
christianalfoni 0acaf5e14c bumped version 2015-08-31 15:57:01 +02:00
Christian Alfoni 034a5725db Merge pull request #193 from davidblurton/react-0.14
React 0.14
2015-08-31 15:55:31 +02:00
David Blurton 38546bc76b Merge remote-tracking branch 'christianalfoni/react-0.14' into react-0.14 2015-08-31 13:51:11 +00:00
David Blurton 2a1be46ffe Add release files 2015-08-31 13:49:21 +00:00
christianalfoni 575321d2e2 Bumped version 2015-08-31 15:33:04 +02:00
Christian Alfoni 3dcfb90044 Merge pull request #192 from davidblurton/react-0.14
React 0.14
2015-08-31 15:24:33 +02:00
David Blurton 87f14ae418 Fix warnings 2015-08-31 13:18:12 +00:00
David Blurton 712f38883b Use react-dom 2015-08-31 13:17:58 +00:00
David Blurton 3258545a55 Fix package.json 2015-08-31 13:17:34 +00:00
David Blurton c0fa87f5cb Merge branch 'master' into react-0.14
Conflicts:
	package.json
2015-08-31 13:01:27 +00:00
David Blurton d623d9ddf1 Depend on beta version of React 2015-08-31 11:22:11 +00:00
iwangu 338ceb853a Update app.js 2015-08-16 21:48:13 +02:00
Matija Marohnić c04623ae05 Make it browserify-friendly
Because `main.js` uses ES2015, this tells browserify to compile it with babelify first.
2015-07-28 16:27:13 +02:00
Matija Marohnić dcac01159a Add example use case for require="isFalse" 2015-07-26 16:44:58 +02:00
Christian Alfoni e38154f73a Merge pull request #173 from silvenon/warnings
Fix React warnings in tests
2015-07-26 16:21:25 +02:00
Christian Alfoni 344154b377 Merge pull request #172 from michaelcontento/relaxed-react-dependency
Relax react peerDependency to minor level (0.13.x instead of ^0.13.1)
2015-07-26 16:20:03 +02:00
christianalfoni dda96c46f6 updated docs 2015-07-26 15:09:23 +02:00
christianalfoni 619a85657f Merge branch 'react-0.14' of https://github.com/christianalfoni/formsy-react into react-0.14 2015-07-26 15:09:17 +02:00
christianalfoni 82edabde15 added HOC and decorators 2015-07-26 15:06:46 +02:00
Matija Marohnić 977bf7caaf Fix React warnings in tests 2015-07-24 18:23:24 +02:00
Michael Contento 4da58c12d7 any bugfix release of react 0.13 is fine for us 2015-07-24 17:23:49 +02:00
Dmitry Semigradsky c84d5e2a45 Merge pull request #160 from reiniergs/master
Update main.js
2015-07-10 20:42:33 +03:00
Reinier Guerra 31e9590172 Update main.js 2015-07-10 11:18:44 -04:00
Reinier Guerra 8691866c0d Update main.js 2015-07-09 23:32:23 -04:00
Semigradsky 357f4adf71 `getErrorMessages` should return empty array when there are no errors 2015-07-09 12:53:26 +03:00
Semigradsky 89759fefde Fix showing multiple error messages 2015-07-09 11:35:52 +03:00
Dmitry Semigradsky b86cca70b3 Edited build badge 2015-07-07 22:26:24 +03:00
Dmitry Semigradsky 9e5723c554 Update package.json
Added missed dependencies
2015-07-07 22:21:04 +03:00
christianalfoni 818b1defbf Initial commit for new version 2015-07-07 19:31:14 +02:00
Dmitry Semigradsky 7e3b4b5e59 Merge pull request #156 from gregors/fix_typo_2
fix typo
2015-07-07 08:02:12 +03:00
Gregory Ostermayr 701e2308a4 fix typo 2015-07-06 17:09:48 -04:00
Christian Alfoni 4279dd59a2 Merge pull request #153 from garbin/master
* Fixed mapModel issue when input name is nested
2015-07-06 11:57:13 +02:00
Christian Alfoni 1c74cb377f Merge pull request #148 from christianalfoni/pass-props
Made passing all props, except `onSubmit`
2015-07-06 11:52:13 +02:00
Garbin Huang 958bd98223 * Fixed mapModel issue when input name is nested 2015-07-01 12:36:45 +08:00
Dmitry Semigradsky 07f2a51a30 Update API.md 2015-06-25 23:42:46 +03:00
Dmitry Semigradsky 114db75de9 Merge pull request #149 from gregors/fix_typo
fix typo
2015-06-24 22:44:25 +03:00
Gregory Ostermayr 8af9abec36 fix typo 2015-06-24 15:41:22 -04:00
Semigradsky 9db2b9fccf Fix tests 2015-06-23 12:55:12 +03:00
Semigradsky c37c053a66 Made passing all props, except `onSubmit` 2015-06-23 12:54:57 +03:00
Christian Alfoni c394ce0925 Merge pull request #138 from christianalfoni/bugfix/validate-empty-fields
Changed validation behaviour
2015-06-09 07:21:16 +02:00
Semigradsky 0395f61233 Changed validation behaviour 2015-06-03 13:10:02 +03:00
christianalfoni d7e7dfc55e Reverted passing of all props, only added autoComplete 2015-05-22 16:20:34 +02:00
christianalfoni 4fcb74f8d8 Bumped version and created release 2015-05-22 14:47:32 +02:00
christianalfoni a1cf2236df Prepared new release 2015-05-22 14:46:45 +02:00
christianalfoni dbb0653a2b Merge branch 'master' of https://github.com/christianalfoni/formsy-react 2015-05-22 13:21:06 +02:00
christianalfoni 23f91c4373 Pull request for isChanged 2015-05-22 13:21:03 +02:00
Christian Alfoni 99500432dc Merge pull request #117 from tomaash/master
Allow form to be initialised with specified data
2015-05-22 13:20:36 +02:00
Tomas Holas 2ef73a2ec2 Merge remote-tracking branch 'upstream/master' 2015-05-16 13:10:16 +02:00
semigradsky cca4973382 Changed loader for examples. 2015-05-15 21:53:08 +03:00
Dmitry Semigradsky d7629a3f6a Merge pull request #125 from kriogenx0/master
typeo in error message
2015-05-15 21:29:06 +03:00
Alex Vaos 5b2ca883f3 typeo in error message 2015-05-15 10:59:16 -07:00
Dmitry Semigradsky 538003698c Update README.md 2015-05-12 12:50:04 +03:00
Semigradsky c9078b69c2 Added testing in io.js 2015-05-11 13:49:01 +03:00
Semigradsky 49d7d39005 💄 cleanup 2015-05-11 13:48:22 +03:00
Dmitry Semigradsky e5d27b832c Merge pull request #119 from esseguin/master
Add type check to isSame
2015-05-08 20:21:05 +03:00
Evan Seguin 5ae260820e adding tests and fixing a bug where an error would be thrown when comparing an object and null 2015-05-08 09:28:58 -07:00
Evan Seguin effa9de53f Adding type checks to prevent errors resulting from assuming two object properties are of the same
type
2015-05-07 11:49:41 -07:00
Evan Seguin d225fa3d2b adding an undefined check to the isSame function 2015-05-07 11:34:16 -07:00
Tomáš Holas 9ab0acff82 Allow form to be initialised with specified data
Modify reset function so that it accepts argument with data model that the form should be initialised with. This comes handy when you want to edit an object to have the form prefilled with object data by just passing the object to reset function instead of filling the fields manualy.
2015-05-04 22:22:05 +02:00
Semigradsky 7ef6c51f1f Updated API 2015-04-29 16:36:09 +03:00
Semigradsky f168b53a10 Add some validation rules 2015-04-29 16:35:53 +03:00
christianalfoni 37018b10fa Merged pull, fixed equality bug and added tests 2015-04-29 14:57:51 +02:00
Christian Alfoni f568ac5970 Merge pull request #115 from christianalfoni/validation-fix
Removed 'required' check from validations
2015-04-29 12:56:43 +02:00
Semigradsky 9a7bff0e03 Removed 'required' check from validations 2015-04-27 12:29:23 +03:00
Dmitry Semigradsky 750fe1508f API corrected 2015-04-27 00:19:03 +03:00
christianalfoni a7ac20ba41 Bumped new version and added release 2015-04-25 12:21:41 +02:00
christianalfoni 9b2d9598e8 added pull request and created tests and documentation 2015-04-25 12:20:39 +02:00
christianalfoni accc0815db Merge branch 'master' of https://github.com/christianalfoni/formsy-react 2015-04-25 12:14:26 +02:00
christianalfoni 8acaeadaee Fixed value bug and configured deploy new version script 2015-04-25 12:14:15 +02:00
Christian Alfoni 58e60aba63 Merge pull request #112 from michaelcontento/patch-2
Fix code example in API.md
2015-04-24 19:47:38 +02:00
Michael Contento cf1550d4f2 Fix code example in API.md 2015-04-24 18:43:35 +02:00
Christian Alfoni 735325a377 Merge pull request #108 from erwanjegouzo/master
don't validate the inputs when the form was not submitted
2015-04-24 14:28:52 +02:00
christianalfoni 6b696d8f03 Preventing any more commits to build 2015-04-24 14:07:41 +02:00
christianalfoni 00cef86cd7 Added build directory for development 2015-04-24 14:07:12 +02:00
Christian Alfoni d90a355436 Update README.md 2015-04-24 14:01:39 +02:00
christianalfoni 2809ea43a4 Brought back Node 0.11 and 0.10 test, misunderstood travis UI 2015-04-24 11:40:29 +02:00
christianalfoni 8970a6756b Running tests only on node 0.12 2015-04-24 11:24:49 +02:00
christianalfoni ce9f566a38 Moved react to dev dependency also 2015-04-24 10:29:33 +02:00
christianalfoni a8231424e9 Moved build image to top 2015-04-24 10:08:55 +02:00
christianalfoni ccdb04c7d9 Added build image 2015-04-24 10:02:33 +02:00
christianalfoni b0467856c5 Added travis build 2015-04-24 09:50:14 +02:00
christianalfoni dd52595dd9 added webpack and jasmine node tests 2015-04-24 09:46:33 +02:00
christianalfoni 6d64dd34b0 Cleaning up tests setup 2015-04-24 08:22:13 +02:00
Erwan Jegouzo 355c0bbee3 don't validate the inputs when the form was not submitted
adding resetForm method
2015-04-22 23:14:24 -04:00
Semigradsky b18b4e8c79 Clean up 2015-04-22 19:06:53 +03:00
christianalfoni f64d2443b9 releasing 0.12.6 2015-04-22 16:54:46 +02:00
christianalfoni 33ae737868 Merge branch 'master' of https://github.com/christianalfoni/formsy-react 2015-04-22 16:37:28 +02:00
christianalfoni d8cd404412 Fixed validation rule value 2015-04-22 16:37:25 +02:00
Christian Alfoni cd37020a04 Merge pull request #105 from erwanjegouzo/master
improving validation rules + unit tests
2015-04-22 16:35:16 +02:00
Erwan Jegouzo 8f4e2566d5 improving validation rules + unit tests 2015-04-21 16:43:14 -04:00
Dmitry Semigradsky e4b5dbf8f9 Added keywords and another recommended fields
For making package easier to discover on bower
2015-04-21 12:42:04 +03:00
Dmitry Semigradsky 1a70c89996 Added keywords
For making package easier to discover on NPM
2015-04-21 12:35:25 +03:00
christianalfoni 4267c40f3b Fixed validation errors using callback 2015-04-17 19:36:28 +02:00
christianalfoni 7e909fb188 Merged new README structure 2015-04-17 14:36:06 +02:00
Dmitry Semigradsky 9eb7d5087b Update README.md 2015-04-17 15:14:06 +03:00
Dmitry Semigradsky 2364e4ffe2 Revert changes 2015-04-17 15:12:20 +03:00
Dmitry Semigradsky 1a450d81f2 Update README.md 2015-04-17 15:09:39 +03:00
Semigradsky 6c3d9a0315 Moved API to separate file 2015-04-17 15:03:05 +03:00
christianalfoni aaac6d0618 Fixed bug with comparing validations. Removed dynamic validations 2015-04-16 17:19:01 +02:00
Dmitry Semigradsky 7227895ee9 Fix README 2015-04-16 10:47:15 +03:00
Dmitry Semigradsky 4736beecb8 Added release badge 2015-04-16 10:45:11 +03:00
christianalfoni 1475dbb88b Fixed required bug 2015-04-15 20:12:40 +02:00
Christian Alfoni 31a78c74de Merge pull request #97 from Semigradsky/master
Examples
2015-04-15 08:42:55 +02:00
Semigradsky 9fdca026bf Added more info about examples. Fix `custom-validation` example 2015-04-15 09:41:30 +03:00
christianalfoni d0af1375a2 Fixed callbacks for onInvalidSubmit and onValidSubmit 2015-04-15 07:28:39 +02:00
christianalfoni e8cd6a8ce8 removed novalidate from readme 2015-04-14 21:32:14 +02:00
christianalfoni 1fb4461121 Removed noValidate and added formNoValidate to readme 2015-04-14 21:31:41 +02:00
Christian Alfoni 261d605c19 Update README.md 2015-04-14 19:07:43 +02:00
Christian Alfoni 2ad4cdd765 Update README.md 2015-04-14 19:07:16 +02:00
Christian Alfoni 01e6b36a36 Update README.md 2015-04-14 19:07:04 +02:00
christianalfoni 54a857638a Release 0.12.0 2015-04-14 19:01:59 +02:00
Christian Alfoni 6eb83c0c8a Update CHANGES.md 2015-04-14 18:51:33 +02:00
christianalfoni 785ac0ca54 Fixed equalsField rule 2015-04-14 08:10:48 +02:00
Christian Alfoni 1e9ff5e527 Merge pull request #86 from TallerWebSolutions/fix-equalsfield
Fixed equalsField validation rule.
2015-04-14 08:05:22 +02:00
Gabriel Neutzling a0bab16ca3 fixed equalsField validation rule. 2015-04-14 00:36:17 -03:00
christianalfoni 617cc21ee5 updated readme 2015-04-13 19:40:08 +02:00
christianalfoni 4d63e3b0b2 Bumped 2015-04-13 19:17:39 +02:00
christianalfoni 8b42fd254a Added string returns error for rules 2015-04-13 19:15:10 +02:00
christianalfoni c5bbc06178 Added missing pull 2015-04-13 18:43:22 +02:00
christianalfoni 4cd34a0084 bumped to 0.11 2015-04-13 18:30:56 +02:00
christianalfoni b0738a5032 Added validation objects and required validation 2015-04-13 18:29:23 +02:00
christianalfoni 36d4439019 added react 0.13 as test lib 2015-04-13 18:29:23 +02:00
christianalfoni 5e00ea281f Passing invalidate on onValidSubmit and onInvalidSubmit 2015-04-13 18:29:22 +02:00
Christian Alfoni fe8ec06af9 Update README.md 2015-04-12 13:23:28 +02:00
Christian Alfoni 82457b66f6 Update README.md 2015-04-12 12:59:40 +02:00
Christian Alfoni 53a4985a7e Merge pull request #79 from Semigradsky/examples
Added example of custom validation
2015-04-11 15:30:16 +02:00
Semigradsky 4a40cd21f1 Add example of custom validation 2015-04-09 13:48:09 +03:00
Christian Alfoni a33a9db353 Update README.md 2015-04-07 19:40:56 +02:00
christianalfoni ad83a10894 Fixed bugs with isValidValue and isValidForm 2015-04-07 07:24:19 +02:00
christianalfoni f1d9d6c15c Added section on validate method 2015-04-03 13:25:04 +02:00
christianalfoni 0964b07e1d Fixed validation bug 2015-04-03 13:20:17 +02:00
Christian Alfoni a348b1aa20 Merge pull request #68 from Semigradsky/npmignore
Added npmignore file
2015-04-01 14:04:46 +02:00
christianalfoni 8cbd6e19ff added internal validation 2015-04-01 13:59:52 +02:00
christianalfoni ebd17fde62 Fixed bug with internal ajax 2015-04-01 13:49:38 +02:00
Christian Alfoni eb3eaa6544 Merge pull request #63 from philippotto/checkValidity
Allow a component to decide whether it is valid (fixes #60)
2015-04-01 13:43:18 +02:00
Christian Alfoni 4814fdbe79 Merge pull request #69 from Semigradsky/examples
Added example
2015-04-01 13:19:42 +02:00
Semigradsky a90b98111f Added example 2015-03-30 13:38:05 +03:00
Semigradsky 5116f5999d Added npmignore file 2015-03-30 11:25:08 +03:00
christianalfoni 47dd80b67f Bumped to release 0.9.0 2015-03-29 13:39:52 +02:00
christianalfoni dca9f78524 Handle any value with tests 2015-03-28 23:12:23 +01:00
Christian Alfoni 703e879f75 Merge pull request #49 from sdemjanenko/fix_initial_false_value
Fix bug: Allow value === false to be passed
2015-03-28 23:10:23 +01:00
Christian Alfoni 716498e6d7 Merge pull request #51 from sdemjanenko/fix_spec_typo
Element spec: Fix typo in spec description
2015-03-28 23:08:02 +01:00
Christian Alfoni c49f7949f2 Merge pull request #55 from Semigradsky/patch-1
Update README.md
2015-03-28 23:02:47 +01:00
Christian Alfoni 6e74d92392 Merge pull request #50 from sdemjanenko/add_validation_tests
Rewrite validation tests
2015-03-28 22:56:42 +01:00
christianalfoni 5e12c95d36 Support React 0.13.1 2015-03-24 07:53:09 +01:00
Philipp Otto 0d34e7b5bb Allow a component to decide whether it is valid (fixes #60) 2015-03-23 00:16:05 +01:00
Christian Alfoni 36d9741535 Merge pull request #61 from michaelcontento/patch-1
Fix global variable leak
2015-03-20 10:53:20 +01:00
Christian Alfoni dd259c5b13 Merge pull request #59 from mistakster/patch-1
Suppress error in non-browser environments
2015-03-20 10:49:14 +01:00
Michael Contento 879b19f2cd Fix global variable leak 2015-03-18 08:10:32 +01:00
Vladimir Kuznetsov 6fcc9391c7 Suppress error in non-browser environments 2015-03-17 11:44:03 +05:00
Dmitry 891526a0fd Update README.md
Added some crosslink for easy navigation.
2015-03-15 12:57:50 +03:00
Stephen Demjanenko 27f705a3d0 Element spec: Fix typo in spec description 2015-03-14 10:43:09 -07:00
Stephen Demjanenko 011d98dfad Rewrite validation tests
Write more tests and share code between them in a describe block with a
beforeEach to set up initial state.
2015-03-14 10:35:21 -07:00
Stephen Demjanenko bade05b9cd Fix bug: Allow value === false to be passed
This is necessary to make a checkbox input work.  Without this change,
value would be set to '' in this case.  Add a test.
2015-03-14 09:45:33 -07:00
Christian Alfoni 2c64535522 Update package.json 2015-03-09 17:22:53 +01:00
Christian Alfoni ba71d27135 Merge pull request #41 from jonaswindey/patch-2
Add repository to package.json (prevents npm WARN)
2015-03-09 17:21:54 +01:00
Jonas Windey 51bdf8da4f Add repository to package.json (prevents npm WARN)
This prevents the npm warning:
``` 
npm WARN package.json formsy-react@0.8.0 No repository field.
```
2015-03-09 08:46:28 +01:00
christianalfoni 86aec656df Fixed dynamic validations, disable form, dynamic value update etc. 2015-03-07 12:51:15 +01:00
christianalfoni d9bf45d417 Added CSRF token handling 2015-03-07 11:12:08 +01:00
Christian Alfoni 6b69c0d8d5 Merge pull request #34 from bryannaegele/master
Added dynamic validation rule support
2015-03-07 10:09:59 +01:00
Christian Alfoni d04bae94f9 Merge pull request #38 from sdemjanenko/settimeout_check_mounted
Check isMounted in setTimeout
2015-03-07 09:51:05 +01:00
Christian Alfoni 5323d08802 Merge pull request #39 from snario/patch-1
Move React to peerDependencies
2015-03-07 09:31:44 +01:00
Liam Horne cef4495d41 Move React to peerDependencies
Using this package and `react` with `npm` causes both scripts to be loaded twice. Peer dependencies is meant to fix this.

See http://blog.nodejs.org/2013/02/07/peer-dependencies/
2015-03-06 19:54:29 -05:00
Stephen Demjanenko a9c4746c21 Check isMounted in setTimeout
This fixes some occasional errors that I would see when causing a form
to unmount.  The unmount would happen before the setTimeout which then
caused setState to be called on an unmounted component.  React throws an
error in this case.

The fix is to check if the component is mounted before running the body
of the setTimeout
2015-03-06 12:19:00 -08:00
Bryan Naegele 7fb17a752d Dynamic validations and requiring
Allow validations to be updated, including requiring
2015-03-02 13:22:11 -06:00
Bryan Naegele 5cc979e249 Dynamic validation support
Add dynamic validation support
2015-03-02 13:08:08 -06:00
Christian Alfoni fa4caa6588 Merge pull request #30 from hahahana/isNumeric
Actually check for invalid floats
2015-02-26 14:15:19 +01:00
Hana Wang 9ef8590f80 Actually check for invalid floats 2015-02-26 04:59:19 -08:00
Christian Alfoni 72a7be5d64 Merge pull request #29 from hahahana/add-csrf-to-header
Attach CSRF token to header if it exists
2015-02-26 13:47:44 +01:00
Christian Alfoni cf93061296 Merge pull request #28 from hahahana/isNumeric
Allow floats in isNumeric
2015-02-26 13:45:42 +01:00
Hana Wang c15b0a200a Attach CSRF token to header if it exists 2015-02-25 10:51:11 -08:00
Hana Wang a65b2943b0 Allow floats for isNumeric 2015-02-25 10:35:30 -08:00
Bryan Naegele a72838b446 Check if form is mounted before calling setState
Check if form is mounted before set state. Cleaned up calls to prop
functions.
2015-02-23 14:33:06 -06:00
christianalfoni 385263c383 Merged two pull requests 2015-02-23 08:55:36 +01:00
Christian Alfoni bbc6ad38e2 Merge pull request #27 from sdemjanenko/update_pristine_values_on_model
Update form's model if the input is pristine and its value changes; test
2015-02-23 07:39:23 +01:00
Christian Alfoni 084aa0772a Merge pull request #26 from sdemjanenko/support_null_children
registerInputs: Support null/undefined children; add test
2015-02-23 07:33:35 +01:00
Stephen Demjanenko 03bc5a4079 Update form's model if the input is pristine and its value changes; test
This fixes a case where a form initializes with default values loaded
from the server.  If an update comes in from the server and the user has
not touched the field (its pristine) then update the field's value.

This feature will help track if the user has actually made any changes
from the default value that the form was/would have been initialized
with.
2015-02-21 13:00:26 -08:00
Stephen Demjanenko e9310002d1 registerInputs: Support null/undefined children; add test
A lot of react examples have rendering functions which early return null
(such as a closed dropdown).  This method was erroring in that case.
2015-02-21 12:16:46 -08:00
Christian Alfoni 86ba085cc4 onChange, dynamic form elements and bug fix 2015-02-03 15:03:55 +01:00
Christian Alfoni 0834810d67 typo 2015-01-21 15:23:35 +01:00
Christian Alfoni f0faede640 Mapping documentation 2015-01-21 15:21:21 +01:00
Christian Alfoni 15b6df9526 Pristine, mapping and tests 2015-01-21 15:16:27 +01:00
Christian Alfoni a008b1ce8d Merge pull request #16 from FoxxMD/master
Add method for checking if input has had interaction
2015-01-21 10:00:01 +01:00
FoxxMD 418c833e2d Add method for checking if input has had interaction
* Input state has `isPristine` to represent user interaction
* `isPristine` defaults to `false`
* Is triggered by `setValue` to `true`
* Reset on `resetValue`
* `setFormPristine` sets all form inputs pristine value
* `onSubmit` sets `setFormPristine` false
2015-01-19 15:26:13 -05:00
Christian Alfoni 1ec5868f64 Fixed bug with handlers in ajax 2015-01-19 18:41:40 +01:00
Christian Alfoni 23bad21119 Merge pull request #13 from smokku/props_callback
Call onSuccess and onSubmitted from props after AJAX response
2015-01-19 18:38:31 +01:00
Tomasz Sterna 196bba3fda Call onSuccess and onSubmitted from props after AJAX response 2015-01-19 14:08:40 +01:00
Christian Alfoni 80227cc50f Added setState documentation 2015-01-17 21:55:18 +01:00
Christian Alfoni 05e3688c8b Added setState documentation 2015-01-17 21:53:05 +01:00
Christian Alfoni 7c95bf191a Update README.md
Documented the value attribute
2015-01-17 09:40:17 +01:00
Christian Alfoni 4127480839 Merge pull request #8 from smokku/empty_headers_forEach
Fixed exception on trying to forEach empty request headers
2015-01-16 17:30:12 +01:00
Christian Alfoni 7163b30246 Merge pull request #9 from smokku/nonJSON__response
Fixed uncaught exception on parsing non-JSON AJAX response
2015-01-16 17:29:33 +01:00
Tomasz Sterna 94e69e7dc0 Fixed exception on trying to forEach empty request headers 2015-01-16 16:04:55 +01:00
Tomasz Sterna 24b9110a62 Fixed uncaught exception on parsing non-JSON AJAX response 2015-01-16 16:00:48 +01:00
Christian Alfoni b8c4414976 Merge branch 'master' of https://github.com/christianalfoni/formsy-react 2015-01-15 12:24:56 +01:00
Christian Alfoni 9e430bbf83 Fixed bug with empty validations 2015-01-14 10:32:31 +01:00
Christian Alfoni e9743a2df1 Cross input validation, invalidateForm and bug fix 2015-01-14 10:15:49 +01:00
Christian Alfoni 7830defb34 Update README.md 2015-01-07 15:02:36 +01:00
Christian Alfoni d813f2ade8 Update README.md 2015-01-07 15:01:36 +01:00
Christian Alfoni 915ddb8cee Fixed required and no validations bug 2015-01-07 11:06:01 +01:00
Christian Alfoni 63938025fa Removed onCancel 2015-01-07 09:53:17 +01:00
Christian Alfoni b04c989cf6 Release 0.4.0 2015-01-07 07:28:15 +01:00
Christian Alfoni dc6ac20285 Merge pull request #6 from LaustAxelsen/patch-2
words, spaces and special letters.
2015-01-07 07:09:18 +01:00
Christian Alfoni 32f67e24ac Merge pull request #5 from LaustAxelsen/patch-1
Allow for no url, using "onSubmit" handler to pass model for manual XHR
2015-01-07 07:04:02 +01:00
LaustAxelsen 4b960bf017 Update README.md 2015-01-06 22:29:26 +01:00
LaustAxelsen 0b0e0a1b49 Update main.js
Include two new basic rules; 
- isWords (a-z, not case sensitive and spaces e.g. "this is a pretty cat")
- isWordsSpecial (a-z, special european letters, not case sensitive and spaces e.g. "sikke en flot ø" or "touché")
2015-01-06 22:24:54 +01:00
LaustAxelsen 2289290998 Update main.js
Added a resetModel method which is also passed to the onSubmit callback.
2015-01-06 22:08:44 +01:00
LaustAxelsen 51bce0315b Update main.js
I wanted to use formsy-react without posting or putting to a specific URL, but instead handle this internally in my single-page-application. So if no url is defined use the "onSubmit" callback with the valid model e.g. {fieldName: "myValue"}.
2015-01-06 21:53:46 +01:00
Christian Alfoni fb47da210f Merge branch 'master' of https://github.com/christianalfoni/formsy-react 2015-01-05 15:04:09 +01:00
Christian Alfoni df4e84a10a Added new handlers 2015-01-05 15:03:01 +01:00
Christian Alfoni d12ae507d0 Update README.md 2014-11-18 09:15:41 +01:00
Christian Alfoni 20f1d71f96 Fixed props check 2014-11-17 11:53:52 +01:00
75 changed files with 5680 additions and 2925 deletions

7
.babelrc Normal file
View File

@ -0,0 +1,7 @@
{
"presets": [
"react",
"es2015",
"stage-2"
]
}

12
.editorconfig Normal file
View File

@ -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

28
.gitignore vendored
View File

@ -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

8
.npmignore Normal file
View File

@ -0,0 +1,8 @@
.babelrc
.editorconfig
.travis.yml
testrunner.js
webpack.production.config.js
examples/
release/
tests/

4
.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: node_js
sudo: false
node_js:
- '4.1'

776
API.md Normal file
View File

@ -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

81
CHANGES.md Normal file
View File

@ -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

View File

@ -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'
]));

View File

@ -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
View File

@ -1,146 +1,112 @@
formsy-react
formsy-react [![GitHub release](https://img.shields.io/github/release/christianalfoni/formsy-react.svg)](https://github.com/christianalfoni/formsy-react/releases) [![Build Status](https://travis-ci.org/christianalfoni/formsy-react.svg?branch=master)](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

View File

@ -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"
]
}

40
examples/README.md Normal file
View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,4 @@
.custom-validation {
width: 500px;
margin: 0 auto;
}

View File

@ -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'));

View File

@ -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>

View File

@ -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;
}

View File

@ -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'));

View File

@ -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>

90
examples/global.css Normal file
View File

@ -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;
}

16
examples/index.html Normal file
View File

@ -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>

4
examples/login/app.css Normal file
View File

@ -0,0 +1,4 @@
.login {
width: 400px;
margin: 0 auto;
}

31
examples/login/app.js Normal file
View File

@ -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'));

14
examples/login/index.html Normal file
View File

@ -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>

View File

@ -0,0 +1,4 @@
.form {
width: 400px;
margin: 0 auto;
}

View File

@ -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'));

View File

@ -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>

View File

@ -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')
})
]
};

View File

@ -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"
}
}

7
release/formsy-react.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

30
src/Decorator.js Normal file
View File

@ -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
});
}
});
};
};

44
src/HOC.js Normal file
View File

@ -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')
);
}

176
src/Mixin.js Normal file
View File

@ -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);
}
};

View File

@ -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;

53
src/utils.js Normal file
View File

@ -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;
}
};

78
src/validationRules.js Normal file
View File

@ -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;

22
testrunner.js Normal file
View File

@ -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);
}
});

568
tests/Element-spec.js Normal file
View File

@ -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();
}
};

748
tests/Formsy-spec.js Executable file
View File

@ -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();
}
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

125
tests/Rules-isFloat-spec.js Normal file
View File

@ -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();
}
};

125
tests/Rules-isInt-spec.js Normal file
View File

@ -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();
}
};

View File

@ -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();
}
}
};

View File

@ -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();
}
};

88
tests/Rules-isUrl-spec.js Normal file
View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
};

View File

@ -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();
}
}
};

37
tests/Utils-spec.js Normal file
View File

@ -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();
}
};

201
tests/Validation-spec.js Normal file
View File

@ -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();
}
};

21
tests/utils/TestInput.js Normal file
View File

@ -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);

View File

@ -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));

3
tests/utils/immediate.js Normal file
View File

@ -0,0 +1,3 @@
export default function (fn) {
setTimeout(fn, 0);
}

View File

@ -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' }
]
}
};