From df4e84a10a9b6731ea81d2b710a28d579d7f3b7d Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Mon, 5 Jan 2015 15:03:01 +0100 Subject: [PATCH] Added new handlers --- .gitignore | 2 + Gulpfile.js | 4 +- README.md | 80 ++--- package.json | 2 +- releases/0.3.0/formsy-react-0.3.0.js | 362 +++++++++++++++++++++++ releases/0.3.0/formsy-react-0.3.0.min.js | 1 + src/main.js | 109 +++---- 7 files changed, 465 insertions(+), 95 deletions(-) create mode 100755 releases/0.3.0/formsy-react-0.3.0.js create mode 100755 releases/0.3.0/formsy-react-0.3.0.min.js diff --git a/.gitignore b/.gitignore index 59d842b..338313e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ node_modules # Users Environment Variables .lock-wscript +.DS_Store +build/ diff --git a/Gulpfile.js b/Gulpfile.js index 6e1b01d..2d3191f 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -19,7 +19,7 @@ var runBrowserifyTask = function (options) { debug: options.debug, // Need that sourcemapping standalone: 'Formsy', // These options are just for Watchify - cache: {}, packageCache: {}, fullPaths: true + cache: {}, packageCache: {}, fullPaths: options.watch }) .require(require.resolve('./src/main.js'), { entry: true }) .transform(reactify) // Transform JSX @@ -91,4 +91,4 @@ gulp.task('deploy', function () { gulp.task('test', shell.task([ './node_modules/.bin/jasmine-node ./specs --autotest --watch ./src --color' -])); \ No newline at end of file +])); diff --git a/README.md b/README.md index eb97de8..151e80d 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,13 @@ A form input builder and validator for React JS - [url](#url) - [method](#method) - [contentType](#contenttype) - - [hideSubmit](#hideSubmit) - - [submitButtonClass](#submitButtonClass) - - [cancelButtonClass](#cancelButtonClass) - - [buttonWrapperClass](#buttonWrapperClass) - [onSuccess()](#onsuccess) - [onSubmit()](#onsubmit) - [onSubmitted()](#onsubmitted) - [onCancel()](#oncancel) - [onError()](#onerror) + - [onValid()](#onvalid) + - [onInvalid()](#oninvalid) - [Formsy.Mixin](#formsymixin) - [name](#name) - [validations](#validations) @@ -53,7 +51,7 @@ The main concept is that forms, inputs and validation is done very differently a 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 @@ -65,6 +63,10 @@ The main concept is that forms, inputs and validation is done very differently a ## Changes +**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 @@ -105,12 +107,21 @@ The main concept is that forms, inputs and validation is done very differently a changeUrl: function () { location.href = '/success'; }, + enableButton: function () { + this.setState({ + canSubmit: true + }); + }, + disableButton: function () { + this.setState({ + canSubmit: false + }); + }, render: function () { return ( - - + - + ); } @@ -119,7 +130,7 @@ The main concept is that forms, inputs and validation is done very differently a 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 is an example of what you can enjoy building +#### Building a form element ```javascript /** @jsx React.DOM */ var Formsy = require('formsy-react'); @@ -163,10 +174,13 @@ So this is basically how you build your form elements. As you can see it is very ```javascript Formsy.defaults({ contentType: 'urlencoded', // default: 'json' + headers: {} // default headers + /* DEPRECATED 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. @@ -199,30 +213,6 @@ Supports **json** (default) and **urlencoded** (x-www-form-urlencoded). **Note!** Response has to be **json**. -#### hideSubmit -```html - -``` -Hides the submit button. Submit is done by ENTER on an input. - -#### submitButtonClass -```html - -``` -Sets a class name on the submit button. - -#### cancelButtonClass -```html - -``` -Sets a class name on the cancel button. - -#### buttonWrapperClass -```html - -``` -Sets a class name on the container that wraps the **submit** and **cancel** buttons. - #### onSuccess(serverResponse) ```html @@ -253,6 +243,18 @@ Will display a "cancel" button next to submit. On click it runs the function han ``` Takes a function to run when the server responds with an error http status code. +#### onValid() +```html + +``` +Whenever the form becomes valid the "onValid" handler is called. Use it to change state of buttons or whatever your heart desires. + +#### onInvalid() +```html + +``` +Whenever the form becomes invalid the "onInvalid" handler is called. Use it to for example revert "onValid" state. + ### Formsy.Mixin #### name @@ -342,7 +344,7 @@ var MyInput = React.createClass({ return (
- +
); } @@ -360,7 +362,7 @@ var MyInput = React.createClass({ return (
- {this.getErrorMessage} + {this.getErrorMessage()}
); } @@ -380,7 +382,7 @@ var MyInput = React.createClass({
{face} - {this.getErrorMessage} + {this.getErrorMessage()}
); } @@ -399,7 +401,7 @@ var MyInput = React.createClass({
{this.props.label} {this.isRequired() ? '*' : null} - {this.getErrorMessage} + {this.getErrorMessage()}
); } @@ -418,7 +420,7 @@ var MyInput = React.createClass({ return (
- {this.getErrorMessage} + {this.getErrorMessage()}
); } @@ -437,7 +439,7 @@ var MyInput = React.createClass({ return (
- {this.getErrorMessage} + {this.getErrorMessage()}
); } diff --git a/package.json b/package.json index 75e480c..1f94d3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.2.3", + "version": "0.3.0", "description": "A form input builder and validator for React JS", "main": "src/main.js", "scripts": { diff --git a/releases/0.3.0/formsy-react-0.3.0.js b/releases/0.3.0/formsy-react-0.3.0.js new file mode 100755 index 0000000..582f758 --- /dev/null +++ b/releases/0.3.0/formsy-react-0.3.0.js @@ -0,0 +1,362 @@ +!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= 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, headers) { + + 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); + + // Add passed headers + Object.keys(headers).forEach(function (header) { + xhr.setRequestHeader(header, headers[header]); + }); + + 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); + }, + + // 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({ + getInitialState: function () { + return { + isValid: true, + isSubmitting: false + }; + }, + getDefaultProps: function () { + return { + headers: {}, + onSuccess: function () {}, + onError: function () {}, + onSubmit: function () {}, + onSubmitted: function () {}, + onValid: function () {}, + onInvalid: 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(); + + var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers; + console.log('headers', headers); + ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json', headers) + .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 && child.props.name) { + child.props._attachToForm = this.attachToForm; + child.props._detachFromForm = this.detachFromForm; + child.props._validate = this.validate; + } + + if (child.props && 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 + }); + + allIsValid && this.props.onValid(); + !allIsValid && this.props.onInvalid(); + }, + + // 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 () { + + return React.DOM.form({ + onSubmit: this.submit, + className: this.props.className + }, + this.props.children + ); + + } +}); + +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"}]},{},[1])(1) +}); \ No newline at end of file diff --git a/releases/0.3.0/formsy-react-0.3.0.min.js b/releases/0.3.0/formsy-react-0.3.0.min.js new file mode 100755 index 0000000..2ae5aed --- /dev/null +++ b/releases/0.3.0/formsy-react-0.3.0.min.js @@ -0,0 +1 @@ +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.Formsy=t()}}(function(){return function t(e,i,n){function o(s,u){if(!i[s]){if(!e[s]){var a="function"==typeof require&&require;if(!u&&a)return a(s,!0);if(r)return r(s,!0);var p=new Error("Cannot find module '"+s+"'");throw p.code="MODULE_NOT_FOUND",p}var F=i[s]={exports:{}};e[s][0].call(F.exports,function(t){var i=e[s][1][t];return o(i?i:t)},F,F.exports,t,e,i,n)}return i[s].exports}for(var r="function"==typeof require&&require,s=0;s=e&&t.length<=i:t.length>=e},equals:function(t,e){return t==e}},s=function(t,e,i){var i=i||[];if("object"==typeof t)for(var n in t)s(t[n],e?e+"["+n+"]":n,i);else i.push(e+"="+encodeURIComponent(t));return i.join("&")},u=function(t,e,i,n,o){var n="urlencoded"===n?"application/"+n.replace("urlencoded","x-www-form-urlencoded"):"application/json";return i="application/json"===n?JSON.stringify(i):s(i),new Promise(function(r,s){try{var u=new XMLHttpRequest;u.open(t,e,!0),u.setRequestHeader("Accept","application/json"),u.setRequestHeader("Content-Type",n),Object.keys(o).forEach(function(t){u.setRequestHeader(t,o[t])}),u.onreadystatechange=function(){4===u.readyState&&(u.status>=200&&u.status<300?r(u.responseText?JSON.parse(u.responseText):null):s(u.responseText?JSON.parse(u.responseText):null))},u.send(i)}catch(a){s(a)}})},a={post:u.bind(null,"POST"),put:u.bind(null,"PUT")},p={};o.defaults=function(t){p=t},o.Mixin={getInitialState:function(){return{_value:this.props.value?this.props.value:"",_isValid:!0}},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");this.props.required&&(this.props.validations=this.props.validations?this.props.validations+",":"",this.props.validations+="isValue"),this.props._attachToForm(this)},componentWillReceiveProps:function(t){t._attachToForm=this.props._attachToForm,t._detachFromForm=this.props._detachFromForm,t._validate=this.props._validate},componentWillUnmount:function(){this.props._detachFromForm(this)},setValue:function(t){this.setState({_value:t},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}},o.addValidationRule=function(t,e){r[t]=e},o.Form=n.createClass({getInitialState:function(){return{isValid:!0,isSubmitting:!1}},getDefaultProps:function(){return{headers:{},onSuccess:function(){},onError:function(){},onSubmit:function(){},onSubmitted:function(){},onValid:function(){},onInvalid:function(){}}},componentWillMount:function(){this.inputs={},this.model={},this.registerInputs(this.props.children)},componentDidMount:function(){this.validateForm()},submit:function(t){if(t.preventDefault(),!this.props.url)throw new Error("Formsy Form needs a url property to post the form");this.updateModel(),this.setState({isSubmitting:!0}),this.props.onSubmit();var e=Object.keys(this.props.headers).length&&this.props.headers||p.headers;console.log("headers",e),a[this.props.method||"post"](this.props.url,this.model,this.props.contentType||p.contentType||"json",e).then(function(t){this.onSuccess(t),this.onSubmitted()}.bind(this)).catch(this.updateInputsWithError)},updateModel:function(){Object.keys(this.inputs).forEach(function(t){var e=this.inputs[t];this.model[t]=e.state._value}.bind(this))},updateInputsWithError:function(t){Object.keys(t).forEach(function(e,i){var n=this.inputs[e],o=[{_isValid:!1,_serverError:t[e]}];i===Object.keys(t).length-1&&o.push(this.validateForm),n.setState.apply(n,o)}.bind(this)),this.setState({isSubmitting:!1}),this.props.onError(t),this.props.onSubmitted()},registerInputs:function(t){n.Children.forEach(t,function(t){t.props&&t.props.name&&(t.props._attachToForm=this.attachToForm,t.props._detachFromForm=this.detachFromForm,t.props._validate=this.validate),t.props&&t.props.children&&this.registerInputs(t.props.children)}.bind(this))},validate:function(t){if(t.props.validations){var e=!0;(t.props.required||""!==t.state._value)&&t.props.validations.split(",").forEach(function(i){var n=i.split(":"),o=n.shift();if(n=n.map(function(t){return JSON.parse(t)}),n=[t.state._value].concat(n),!r[o])throw new Error("Formsy does not have the validation rule: "+o);r[o].apply(null,n)||(e=!1)}),t.setState({_isValid:e,_serverError:null},this.validateForm)}},validateForm:function(){var t=!0,e=this.inputs;Object.keys(e).forEach(function(i){e[i].state._isValid||(t=!1)}),this.setState({isValid:t}),t&&this.props.onValid(),!t&&this.props.onInvalid()},attachToForm:function(t){this.inputs[t.props.name]=t,this.model[t.props.name]=t.state._value,this.validate(t)},detachFromForm:function(t){delete this.inputs[t.props.name],delete this.model[t.props.name]},render:function(){return n.DOM.form({onSubmit:this.submit,className:this.props.className},this.props.children)}}),i.exports||i.module||i.define&&i.define.amd||(i.Formsy=o),e.exports=o}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{react:"react"}]},{},[1])(1)}); \ No newline at end of file diff --git a/src/main.js b/src/main.js index 95455bc..e9d2196 100644 --- a/src/main.js +++ b/src/main.js @@ -26,18 +26,19 @@ var validationRules = { return value == eql; } }; -var toURLEncoded = function (element,key,list){ + +var toURLEncoded = function (element, key, list) { var list = list || []; - if(typeof(element)=='object'){ + if (typeof (element) == 'object') { for (var idx in element) - toURLEncoded(element[idx],key?key+'['+idx+']':idx,list); + toURLEncoded(element[idx], key ? key + '[' + idx + ']' : idx, list); } else { - list.push(key+'='+encodeURIComponent(element)); + list.push(key + '=' + encodeURIComponent(element)); } return list.join('&'); }; -var request = function (method, url, data, contentType) { +var request = function (method, url, data, contentType, headers) { var contentType = contentType === 'urlencoded' ? 'application/' + contentType.replace('urlencoded', 'x-www-form-urlencoded') : 'application/json'; data = contentType === 'application/json' ? JSON.stringify(data) : toURLEncoded(data); @@ -48,6 +49,12 @@ var request = function (method, url, data, contentType) { xhr.open(method, url, true); xhr.setRequestHeader('Accept', 'application/json'); xhr.setRequestHeader('Content-Type', contentType); + + // Add passed headers + Object.keys(headers).forEach(function (header) { + xhr.setRequestHeader(header, headers[header]); + }); + xhr.onreadystatechange = function () { if (xhr.readyState === 4) { @@ -78,26 +85,26 @@ Formsy.defaults = function (passedOptions) { Formsy.Mixin = { getInitialState: function () { - return { - _value: this.props.value ? this.props.value : '', - _isValid: true - }; + 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.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._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); + 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 @@ -109,16 +116,16 @@ Formsy.Mixin = { // Detach it when component unmounts componentWillUnmount: function () { - this.props._detachFromForm(this); + 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)); + this.setState({ + _value: value + }, function () { + this.props._validate(this); + }.bind(this)); }, resetValue: function () { this.setState({ @@ -163,11 +170,14 @@ Formsy.Form = React.createClass({ }, getDefaultProps: function () { return { + headers: {}, onSuccess: function () {}, onError: function () {}, onSubmit: function () {}, - onSubmitted: function () {} - } + onSubmitted: function () {}, + onValid: function () {}, + onInvalid: function () {} + }; }, // Add a map to store the inputs of the form, a model to store @@ -194,8 +204,12 @@ Formsy.Form = React.createClass({ this.setState({ isSubmitting: true }); + this.props.onSubmit(); - ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json') + + var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers; + console.log('headers', headers); + ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json', headers) .then(function (response) { this.onSuccess(response); this.onSubmitted(); @@ -269,7 +283,9 @@ Formsy.Form = React.createClass({ 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 = 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); @@ -292,7 +308,7 @@ Formsy.Form = React.createClass({ validateForm: function () { var allIsValid = true; var inputs = this.inputs; - + Object.keys(inputs).forEach(function (name) { if (!inputs[name].state._isValid) { allIsValid = false; @@ -302,6 +318,9 @@ Formsy.Form = React.createClass({ this.setState({ isValid: allIsValid }); + + allIsValid && this.props.onValid(); + !allIsValid && this.props.onInvalid(); }, // Method put on each input component to register @@ -319,30 +338,14 @@ Formsy.Form = React.createClass({ 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 - ) + onSubmit: this.submit, + className: this.props.className + }, + this.props.children ); - + } }); @@ -350,4 +353,4 @@ if (!global.exports && !global.module && (!global.define || !global.define.amd)) global.Formsy = Formsy; } -module.exports = Formsy; \ No newline at end of file +module.exports = Formsy;