diff --git a/releases/formsy-react.js b/releases/formsy-react.js index 01840b0..749f79e 100755 --- a/releases/formsy-react.js +++ b/releases/formsy-react.js @@ -66,10 +66,15 @@ var request = function (method, url, data, contentType, headers) { 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); + try { + var response = xhr.responseText ? JSON.parse(xhr.responseText) : null; + if (xhr.status >= 200 && xhr.status < 300) { + resolve(response); + } else { + reject(response); + } + } catch (e) { + reject(e); } } @@ -95,7 +100,8 @@ Formsy.Mixin = { getInitialState: function () { return { _value: this.props.value ? this.props.value : '', - _isValid: true + _isValid: true, + _isPristine: true }; }, componentWillMount: function () { @@ -133,14 +139,16 @@ Formsy.Mixin = { // We validate after the value has been set setValue: function (value) { this.setState({ - _value: value + _value: value, + _isPristine: false }, function () { this.props._validate(this); }.bind(this)); }, resetValue: function () { this.setState({ - _value: '' + _value: '', + _isPristine: true }, function () { this.props._validate(this); }); @@ -157,6 +165,9 @@ Formsy.Mixin = { isValid: function () { return this.state._isValid; }, + isPristine: function () { + return this.state._isPristine; + }, isRequired: function () { return this.props.required; }, @@ -207,6 +218,11 @@ Formsy.Form = React.createClass({ submit: function (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); + // To support use cases where no async or request operation is needed. // The "onSubmit" callback is called with the model e.g. {fieldName: "myValue"}, // if wanting to reset the entire form to original state, the second param is a callback for this. @@ -223,12 +239,12 @@ Formsy.Form = React.createClass({ this.props.onSubmit(); - var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers; + var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.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(); + this.props.onSuccess(response); + this.props.onSubmitted(); }.bind(this)) .catch(this.failSubmit); }, @@ -305,6 +321,20 @@ Formsy.Form = React.createClass({ }.bind(this), {}); }, + setFormPristine: function(isPristine) { + var inputs = this.inputs; + var inputKeys = Object.keys(inputs); + + // Iterate through each component and set it as pristine + // or "dirty". + inputKeys.forEach(function (name, index) { + var component = inputs[name]; + component.setState({ + _isPristine: isPristine + }); + }.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 diff --git a/releases/formsy-react.min.js b/releases/formsy-react.min.js index a5fbe6f..4e07159 100755 --- a/releases/formsy-react.min.js +++ b/releases/formsy-react.min.js @@ -1 +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 r(o,u){if(!i[o]){if(!e[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(s)return s(o,!0);var p=new Error("Cannot find module '"+o+"'");throw p.code="MODULE_NOT_FOUND",p}var h=i[o]={exports:{}};e[o][0].call(h.exports,function(t){var i=e[o][1][t];return r(i?i:t)},h,h.exports,t,e,i,n)}return i[o].exports}for(var s="function"==typeof require&&require,o=0;o=e&&t.length<=i:t.length>=e},equals:function(t,e){return t==e}},o=function(t,e,i){var i=i||[];if("object"==typeof t)for(var n in t)o(t[n],e?e+"["+n+"]":n,i);else i.push(e+"="+encodeURIComponent(t));return i.join("&")},u=function(t,e,i,n,r){var n="urlencoded"===n?"application/"+n.replace("urlencoded","x-www-form-urlencoded"):"application/json";return i="application/json"===n?JSON.stringify(i):o(i),new Promise(function(s,o){try{var u=new XMLHttpRequest;u.open(t,e,!0),u.setRequestHeader("Accept","application/json"),u.setRequestHeader("Content-Type",n),Object.keys(r).forEach(function(t){u.setRequestHeader(t,r[t])}),u.onreadystatechange=function(){4===u.readyState&&(u.status>=200&&u.status<300?s(u.responseText?JSON.parse(u.responseText):null):o(u.responseText?JSON.parse(u.responseText):null))},u.send(i)}catch(a){o(a)}})},a={post:u.bind(null,"POST"),put:u.bind(null,"PUT")},p={};r.defaults=function(t){p=t},r.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._validations=this.props.validations||"",this.props.required&&(this._validations=this.props.validations?this.props.validations+",":"",this._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}},r.addValidationRule=function(t,e){s[t]=e},r.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)return this.updateModel(),void this.props.onSubmit(this.model,this.resetModel,this.updateInputsWithError);this.updateModel(),this.setState({isSubmitting:!0}),this.props.onSubmit();var e=Object.keys(this.props.headers).length&&this.props.headers||p.headers;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.failSubmit)},updateModel:function(){Object.keys(this.inputs).forEach(function(t){var e=this.inputs[t];this.model[t]=e.state._value}.bind(this))},resetModel:function(){Object.keys(this.inputs).forEach(function(t){this.inputs[t].resetValue()}.bind(this)),this.validateForm()},updateInputsWithError:function(t){Object.keys(t).forEach(function(e){var i=this.inputs[e];if(!i)throw new Error("You are trying to update an input that does not exists. Verify errors object with input names. "+JSON.stringify(t));var n=[{_isValid:!1,_serverError:t[e]}];i.setState.apply(i,n)}.bind(this))},failSubmit:function(t){this.updateInputsWithError(t),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))},getCurrentValues:function(){return Object.keys(this.inputs).reduce(function(t,e){var i=this.inputs[e];return t[e]=i.state._value,t}.bind(this),{})},validate:function(t){if(t.props.required||t._validations){var e=this.runValidation(t);t.setState({_isValid:e,_serverError:null},this.validateForm)}},runValidation:function(t){var e=!0;return t._validations.length&&(t.props.required||""!==t.state._value)&&t._validations.split(",").forEach(function(i){var n=i.split(":"),r=n.shift();if(n=n.map(function(t){try{return JSON.parse(t)}catch(e){return t}}),n=[t.state._value].concat(n),!s[r])throw new Error("Formsy does not have the validation rule: "+r);s[r].apply(this.getCurrentValues(),n)||(e=!1)}.bind(this)),e},validateForm:function(){var t=!0,e=this.inputs,i=Object.keys(e),n=function(){i.forEach(function(i){e[i].state._isValid||(t=!1)}.bind(this)),this.setState({isValid:t}),t&&this.props.onValid(),!t&&this.props.onInvalid()}.bind(this);i.forEach(function(t,r){var s=e[t],o=this.runValidation(s);s.setState({_isValid:o,_serverError:null},r===i.length-1?n:null)}.bind(this))},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=r),e.exports=r}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{react:"react"}]},{},[1])(1)}); \ No newline at end of file +!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i;"undefined"!=typeof window?i=window:"undefined"!=typeof global?i=global:"undefined"!=typeof self&&(i=self),i.Formsy=t()}}(function(){return function t(i,e,n){function r(o,u){if(!e[o]){if(!i[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(s)return s(o,!0);var p=new Error("Cannot find module '"+o+"'");throw p.code="MODULE_NOT_FOUND",p}var h=e[o]={exports:{}};i[o][0].call(h.exports,function(t){var e=i[o][1][t];return r(e?e:t)},h,h.exports,t,i,e,n)}return e[o].exports}for(var s="function"==typeof require&&require,o=0;o=i&&t.length<=e:t.length>=i},equals:function(t,i){return t==i}},o=function(t,i,e){var e=e||[];if("object"==typeof t)for(var n in t)o(t[n],i?i+"["+n+"]":n,e);else e.push(i+"="+encodeURIComponent(t));return e.join("&")},u=function(t,i,e,n,r){var n="urlencoded"===n?"application/"+n.replace("urlencoded","x-www-form-urlencoded"):"application/json";return e="application/json"===n?JSON.stringify(e):o(e),new Promise(function(s,o){try{var u=new XMLHttpRequest;u.open(t,i,!0),u.setRequestHeader("Accept","application/json"),u.setRequestHeader("Content-Type",n),Object.keys(r).forEach(function(t){u.setRequestHeader(t,r[t])}),u.onreadystatechange=function(){if(4===u.readyState)try{var t=u.responseText?JSON.parse(u.responseText):null;u.status>=200&&u.status<300?s(t):o(t)}catch(i){o(i)}},u.send(e)}catch(a){o(a)}})},a={post:u.bind(null,"POST"),put:u.bind(null,"PUT")},p={};r.defaults=function(t){p=t},r.Mixin={getInitialState:function(){return{_value:this.props.value?this.props.value:"",_isValid:!0,_isPristine:!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._validations=this.props.validations||"",this.props.required&&(this._validations=this.props.validations?this.props.validations+",":"",this._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,_isPristine:!1},function(){this.props._validate(this)}.bind(this))},resetValue:function(){this.setState({_value:"",_isPristine:!0},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},isPristine:function(){return this.state._isPristine},isRequired:function(){return this.props.required},showRequired:function(){return this.props.required&&""===this.state._value},showError:function(){return!this.showRequired()&&!this.state._isValid}},r.addValidationRule=function(t,i){s[t]=i},r.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.setFormPristine(!1),!this.props.url)return this.updateModel(),void this.props.onSubmit(this.model,this.resetModel,this.updateInputsWithError);this.updateModel(),this.setState({isSubmitting:!0}),this.props.onSubmit();var i=Object.keys(this.props.headers).length&&this.props.headers||p.headers||{};a[this.props.method||"post"](this.props.url,this.model,this.props.contentType||p.contentType||"json",i).then(function(t){this.props.onSuccess(t),this.props.onSubmitted()}.bind(this))["catch"](this.failSubmit)},updateModel:function(){Object.keys(this.inputs).forEach(function(t){var i=this.inputs[t];this.model[t]=i.state._value}.bind(this))},resetModel:function(){Object.keys(this.inputs).forEach(function(t){this.inputs[t].resetValue()}.bind(this)),this.validateForm()},updateInputsWithError:function(t){Object.keys(t).forEach(function(i){var e=this.inputs[i];if(!e)throw new Error("You are trying to update an input that does not exists. Verify errors object with input names. "+JSON.stringify(t));var n=[{_isValid:!1,_serverError:t[i]}];e.setState.apply(e,n)}.bind(this))},failSubmit:function(t){this.updateInputsWithError(t),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))},getCurrentValues:function(){return Object.keys(this.inputs).reduce(function(t,i){var e=this.inputs[i];return t[i]=e.state._value,t}.bind(this),{})},setFormPristine:function(t){var i=this.inputs,e=Object.keys(i);e.forEach(function(e){var n=i[e];n.setState({_isPristine:t})}.bind(this))},validate:function(t){if(t.props.required||t._validations){var i=this.runValidation(t);t.setState({_isValid:i,_serverError:null},this.validateForm)}},runValidation:function(t){var i=!0;return t._validations.length&&(t.props.required||""!==t.state._value)&&t._validations.split(",").forEach(function(e){var n=e.split(":"),r=n.shift();if(n=n.map(function(t){try{return JSON.parse(t)}catch(i){return t}}),n=[t.state._value].concat(n),!s[r])throw new Error("Formsy does not have the validation rule: "+r);s[r].apply(this.getCurrentValues(),n)||(i=!1)}.bind(this)),i},validateForm:function(){var t=!0,i=this.inputs,e=Object.keys(i),n=function(){e.forEach(function(e){i[e].state._isValid||(t=!1)}.bind(this)),this.setState({isValid:t}),t&&this.props.onValid(),!t&&this.props.onInvalid()}.bind(this);e.forEach(function(t,r){var s=i[t],o=this.runValidation(s);s.setState({_isValid:o,_serverError:null},r===e.length-1?n:null)}.bind(this))},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)}}),e.exports||e.module||e.define&&e.define.amd||(e.Formsy=r),i.exports=r}).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 029f69c..102062a 100644 --- a/src/main.js +++ b/src/main.js @@ -98,7 +98,8 @@ Formsy.Mixin = { getInitialState: function () { return { _value: this.props.value ? this.props.value : '', - _isValid: true + _isValid: true, + _isPristine: true }; }, componentWillMount: function () { @@ -136,14 +137,16 @@ Formsy.Mixin = { // We validate after the value has been set setValue: function (value) { this.setState({ - _value: value + _value: value, + _isPristine: false }, function () { this.props._validate(this); }.bind(this)); }, resetValue: function () { this.setState({ - _value: '' + _value: '', + _isPristine: true }, function () { this.props._validate(this); }); @@ -160,6 +163,9 @@ Formsy.Mixin = { isValid: function () { return this.state._isValid; }, + isPristine: function () { + return this.state._isPristine; + }, isRequired: function () { return this.props.required; }, @@ -210,6 +216,11 @@ Formsy.Form = React.createClass({ submit: function (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); + // To support use cases where no async or request operation is needed. // The "onSubmit" callback is called with the model e.g. {fieldName: "myValue"}, // if wanting to reset the entire form to original state, the second param is a callback for this. @@ -308,6 +319,20 @@ Formsy.Form = React.createClass({ }.bind(this), {}); }, + setFormPristine: function(isPristine) { + var inputs = this.inputs; + var inputKeys = Object.keys(inputs); + + // Iterate through each component and set it as pristine + // or "dirty". + inputKeys.forEach(function (name, index) { + var component = inputs[name]; + component.setState({ + _isPristine: isPristine + }); + }.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