diff --git a/bower.json b/bower.json index 02f6bb1..3c248fa 100644 --- a/bower.json +++ b/bower.json @@ -1,8 +1,8 @@ { "name": "formsy-react", - "version": "0.11.2", + "version": "0.12.0", "main": "src/main.js", "dependencies": { - "react": "^0.11.2 || ^0.13.1" + "react": "^0.13.1" } } diff --git a/package.json b/package.json index 6565acc..77c30a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.11.2", + "version": "0.12.0", "description": "A form input builder and validator for React JS", "repository": { "type": "git", @@ -34,6 +34,6 @@ "webpack-dev-server": "^1.7.0" }, "peerDependencies": { - "react": "^0.12.2 || ^0.13.1" + "react": "^0.13.1" } } diff --git a/release/formsy-react.js b/release/formsy-react.js index c1c8dfe..c52e0f5 100644 --- a/release/formsy-react.js +++ b/release/formsy-react.js @@ -45,7 +45,6 @@ Formsy.Form = React.createClass({displayName: "Form", componentWillMount: function () { this.inputs = {}; this.model = {}; - this.registerInputs(this.props.children); }, componentDidMount: function () { @@ -63,9 +62,7 @@ Formsy.Form = React.createClass({displayName: "Form", // update if (this.isMounted()) { - this.registerInputs(this.props.children); - - if (this.props.validationErrors) { + if (this.props.validationErrors) { this.setInputValidationErrors(this.props.validationErrors); } @@ -87,34 +84,11 @@ Formsy.Form = React.createClass({displayName: "Form", // 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. - if (!this.props.url) { - this.updateModel(); - var model = this.mapModel(); - this.props.onSubmit(model, this.resetModel, this.updateInputsWithError); - this.state.isValid ? this.props.onValidSubmit(model, this.resetModel) : this.props.onInvalidSubmit(model, this.resetModel); - return; - } - this.updateModel(); - this.setState({ - isSubmitting: true - }); + var model = this.mapModel(); + this.props.onSubmit(model, this.resetModel, this.updateInputsWithError); + this.state.isValid ? this.props.onValidSubmit(model, this.resetModel) : this.props.onInvalidSubmit(model, this.resetModel); - this.props.onSubmit(this.mapModel(), this.resetModel, this.updateInputsWithError); - - var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers || {}; - - var method = this.props.method && utils.ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post'; - utils.ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers) - .then(function (response) { - this.props.onSuccess(response); - this.props.onSubmitted(); - }.bind(this)) - .catch(this.failSubmit); }, mapModel: function () { @@ -139,14 +113,14 @@ Formsy.Form = React.createClass({displayName: "Form", }, setInputValidationErrors: function (errors) { - Object.keys(this.inputs).forEach(function (name, index) { + Object.keys(this.inputs).forEach(function (name, index) { var component = this.inputs[name]; var args = [{ _isValid: !(name in errors), _serverError: errors[name] }]; component.setState.apply(component, args); - }.bind(this)); + }.bind(this)); }, // Go through errors from server and grab the components @@ -162,42 +136,43 @@ Formsy.Form = React.createClass({displayName: "Form", var args = [{ _isValid: false, - _serverError: errors[name] + _validationError: errors[name] }]; component.setState.apply(component, args); }.bind(this)); }, - failSubmit: function (errors) { - this.updateInputsWithError(errors); - 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) { + traverseChildrenAndRegisterInputs: function (children) { - if (child && child.props && child.props.name) { - child.props._attachToForm = this.attachToForm; - child.props._detachFromForm = this.detachFromForm; - child.props._validate = this.validate; - child.props._isFormDisabled = this.isFormDisabled; - child.props._isValidValue = function (component, value) { - return this.runValidation(component, value).isValid; - }.bind(this); + if (typeof children !== 'object' || children === null) { + return children; + } + return React.Children.map(children, function (child) { + + if (typeof child !== 'object' || child === null) { + return child; } - if (child && child.props && child.props.children) { - this.registerInputs(child.props.children); + if (child.props && child.props.name) { + + return React.cloneElement(child, { + _attachToForm: this.attachToForm, + _detachFromForm: this.detachFromForm, + _validate: this.validate, + _isFormDisabled: this.isFormDisabled, + _isValidValue: function (component, value) { + return this.runValidation(component, value).isValid; + }.bind(this) + }, child.props && child.props.children); + } else { + return React.cloneElement(child, {}, this.traverseChildrenAndRegisterInputs(child.props && child.props.children)); } - }.bind(this)); + }, this); + }, isFormDisabled: function () { @@ -230,7 +205,7 @@ Formsy.Form = React.createClass({displayName: "Form", // validate the input and set its state. Then check the // state of the form itself validate: function (component) { - + // Trigger onChange if (this.state.canChange) { this.props.onChange(this.getCurrentValues()); @@ -273,7 +248,7 @@ Formsy.Form = React.createClass({displayName: "Form", if (isValid && !isRequired) { return ''; - } + } if (validationResults.errors.length) { return validationResults.errors[0]; @@ -314,57 +289,6 @@ Formsy.Form = React.createClass({displayName: "Form", 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); - - }); - } - - return results; - - }, - -/* - - 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') { @@ -381,7 +305,9 @@ Formsy.Form = React.createClass({displayName: "Form", results.errors.push(validation); results.failed.push(validationMethod); } else if (!validation) { - results.failed.push(validationMethod); + results.failed.push(validationMethod); + } else { + results.success.push(validationMethod); } return; @@ -393,7 +319,9 @@ Formsy.Form = React.createClass({displayName: "Form", } return results; -*/ + + }, + // Validate the form by going through all child input components // and check their state validateForm: function () { @@ -466,9 +394,10 @@ Formsy.Form = React.createClass({displayName: "Form", return React.DOM.form({ onSubmit: this.submit, - className: this.props.className + className: this.props.className, + novalidate: 'novalidate' in this.props ? this.props.novalidate : true }, - this.props.children + this.traverseChildrenAndRegisterInputs(this.props.children) ); } @@ -554,11 +483,6 @@ module.exports = { // 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; - nextProps._isValidValue = this.props._isValidValue; - nextProps._isFormDisabled = this.props._isFormDisabled; this.setValidations(nextProps.validations, nextProps.required); }, @@ -679,7 +603,7 @@ module.exports = { return value === ''; }, 'isEmail': function (values, value) { - return value !== undefined && 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); + return !value || 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 (values, value) { return value === true; @@ -700,13 +624,13 @@ module.exports = { } }, 'isAlpha': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z]+$/); + return !value || value.match(/^[a-zA-Z]+$/); }, 'isWords': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z\s]+$/); + return !value || value.match(/^[a-zA-Z\s]+$/); }, 'isSpecialWords': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/); + return !value || value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/); }, isLength: function (values, value, length) { return value !== undefined && value.length === length; diff --git a/release/formsy-react.min.js b/release/formsy-react.min.js index 0ff29bf..ae36d01 100644 --- a/release/formsy-react.min.js +++ b/release/formsy-react.min.js @@ -1 +1 @@ -!function t(i,s,r){function e(o,u){if(!s[o]){if(!i[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(n)return n(o,!0);var h=new Error("Cannot find module '"+o+"'");throw h.code="MODULE_NOT_FOUND",h}var d=s[o]={exports:{}};i[o][0].call(d.exports,function(t){var s=i[o][1][t];return e(s?s:t)},d,d.exports,t,i,s,r)}return s[o].exports}for(var n="function"==typeof require&&require,o=0;o1)throw new Error("Formsy does not support multiple args on string validations. Use object format of validations instead.");return t[r]=s[0]||!0,t},{}):t||{}};i.exports={getInitialState:function(){return{_value:this.props.value,_isRequired:!1,_isValid:!0,_isPristine:!0,_pristineValue:this.props.value,_validationError:""}},getDefaultProps:function(){return{validationError:"",validationErrors:{}}},componentWillMount:function(){var t=function(){this.setValidations(this.props.validations,this.props.required),this.props._attachToForm(this)}.bind(this);if(!this.props.name)throw new Error("Form Input requires a name property when used");return this.props._attachToForm?(t(),void 0):setTimeout(function(){if(this.isMounted()){if(!this.props._attachToForm)throw new Error("Form Mixin requires component to be nested in a Form");t()}}.bind(this),0)},componentWillReceiveProps:function(t){t._attachToForm=this.props._attachToForm,t._detachFromForm=this.props._detachFromForm,t._validate=this.props._validate,t._isValidValue=this.props._isValidValue,t._isFormDisabled=this.props._isFormDisabled,this.setValidations(t.validations,t.required)},componentDidUpdate:function(t){var i=function(){return this.props.value!==t.value&&this.state._value===t.value}.bind(this);(t.validations!==this.props.validations||i())&&this.setValue(this.props.value)},componentWillUnmount:function(){this.props._detachFromForm(this)},setValidations:function(t,i){this._validations=s(t)||{},this._requiredValidations=i===!0?{isDefaultRequiredValue:!0}:s(i)},setValue:function(t){this.setState({_value:t,_isPristine:!1},function(){this.props._validate(this)}.bind(this))},resetValue:function(){this.setState({_value:this.state._pristineValue,_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()?this.state._validationError:null},isFormDisabled:function(){return this.props._isFormDisabled()},isValid:function(){return this.state._isValid},isPristine:function(){return this.state._isPristine},isRequired:function(){return!!this.props.required},showRequired:function(){return this.state._isRequired},showError:function(){return!this.showRequired()&&!this.isValid()},isValidValue:function(t){return this.props._isValidValue.call(null,this,t)}}},{}],3:[function(t,i){i.exports={arraysDiffer:function(t,i){var s=!1;return t.length!==i.length?s=!0:t.forEach(function(t,r){t!==i[r]&&(s=!0)}),s}}},{}],4:[function(t,i){i.exports={isDefaultRequiredValue:function(t,i){return void 0===i||""===i},hasValue:function(t,i){return void 0!==i},matchRegexp:function(t,i,s){return void 0!==i&&!!i.match(s)},isUndefined:function(t,i){return void 0===i},isEmptyString:function(t,i){return""===i},isEmail:function(t,i){return void 0!==i&&i.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(t,i){return i===!0},isFalse:function(t,i){return i===!1},isNumeric:function(t,i){if("number"==typeof i)return!0;var s=void 0!==i&&i.match(/[-+]?(\d*[.])?\d+/);return s?s[0]==i:!1},isAlpha:function(t,i){return void 0!==i&&i.match(/^[a-zA-Z]+$/)},isWords:function(t,i){return void 0!==i&&i.match(/^[a-zA-Z\s]+$/)},isSpecialWords:function(t,i){return void 0!==i&&i.match(/^[a-zA-Z\s\u00C0-\u017F]+$/)},isLength:function(t,i,s){return void 0!==i&&i.length===s},equals:function(t,i,s){return i==s},equalsField:function(t,i,s){return i==t[s]},maxLength:function(t,i,s){return void 0!==i&&i.length<=s},minLength:function(t,i,s){return void 0!==i&&i.length>=s}}},{}]},{},[1]); \ No newline at end of file +!function t(i,n,e){function r(o,u){if(!n[o]){if(!i[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(s)return s(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var d=n[o]={exports:{}};i[o][0].call(d.exports,function(t){var n=i[o][1][t];return r(n?n:t)},d,d.exports,t,i,n,e)}return n[o].exports}for(var s="function"==typeof require&&require,o=0;o1)throw new Error("Formsy does not support multiple args on string validations. Use object format of validations instead.");return t[e]=n[0]||!0,t},{}):t||{}};i.exports={getInitialState:function(){return{_value:this.props.value,_isRequired:!1,_isValid:!0,_isPristine:!0,_pristineValue:this.props.value,_validationError:""}},getDefaultProps:function(){return{validationError:"",validationErrors:{}}},componentWillMount:function(){var t=function(){this.setValidations(this.props.validations,this.props.required),this.props._attachToForm(this)}.bind(this);if(!this.props.name)throw new Error("Form Input requires a name property when used");return this.props._attachToForm?(t(),void 0):setTimeout(function(){if(this.isMounted()){if(!this.props._attachToForm)throw new Error("Form Mixin requires component to be nested in a Form");t()}}.bind(this),0)},componentWillReceiveProps:function(t){this.setValidations(t.validations,t.required)},componentDidUpdate:function(t){var i=function(){return this.props.value!==t.value&&this.state._value===t.value}.bind(this);(t.validations!==this.props.validations||i())&&this.setValue(this.props.value)},componentWillUnmount:function(){this.props._detachFromForm(this)},setValidations:function(t,i){this._validations=n(t)||{},this._requiredValidations=i===!0?{isDefaultRequiredValue:!0}:n(i)},setValue:function(t){this.setState({_value:t,_isPristine:!1},function(){this.props._validate(this)}.bind(this))},resetValue:function(){this.setState({_value:this.state._pristineValue,_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()?this.state._validationError:null},isFormDisabled:function(){return this.props._isFormDisabled()},isValid:function(){return this.state._isValid},isPristine:function(){return this.state._isPristine},isRequired:function(){return!!this.props.required},showRequired:function(){return this.state._isRequired},showError:function(){return!this.showRequired()&&!this.isValid()},isValidValue:function(t){return this.props._isValidValue.call(null,this,t)}}},{}],3:[function(t,i){i.exports={arraysDiffer:function(t,i){var n=!1;return t.length!==i.length?n=!0:t.forEach(function(t,e){t!==i[e]&&(n=!0)}),n}}},{}],4:[function(t,i){i.exports={isDefaultRequiredValue:function(t,i){return void 0===i||""===i},hasValue:function(t,i){return void 0!==i},matchRegexp:function(t,i,n){return void 0!==i&&!!i.match(n)},isUndefined:function(t,i){return void 0===i},isEmptyString:function(t,i){return""===i},isEmail:function(t,i){return!i||i.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(t,i){return i===!0},isFalse:function(t,i){return i===!1},isNumeric:function(t,i){if("number"==typeof i)return!0;var n=void 0!==i&&i.match(/[-+]?(\d*[.])?\d+/);return n?n[0]==i:!1},isAlpha:function(t,i){return!i||i.match(/^[a-zA-Z]+$/)},isWords:function(t,i){return!i||i.match(/^[a-zA-Z\s]+$/)},isSpecialWords:function(t,i){return!i||i.match(/^[a-zA-Z\s\u00C0-\u017F]+$/)},isLength:function(t,i,n){return void 0!==i&&i.length===n},equals:function(t,i,n){return i==n},equalsField:function(t,i,n){return i==t[n]},maxLength:function(t,i,n){return void 0!==i&&i.length<=n},minLength:function(t,i,n){return void 0!==i&&i.length>=n}}},{}]},{},[1]); \ No newline at end of file diff --git a/src/Mixin.js b/src/Mixin.js index 009229f..68e6983 100644 --- a/src/Mixin.js +++ b/src/Mixin.js @@ -68,11 +68,6 @@ module.exports = { // 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; - nextProps._isValidValue = this.props._isValidValue; - nextProps._isFormDisabled = this.props._isFormDisabled; this.setValidations(nextProps.validations, nextProps.required); }, diff --git a/src/main.js b/src/main.js index c1206c8..055b501 100644 --- a/src/main.js +++ b/src/main.js @@ -43,7 +43,6 @@ Formsy.Form = React.createClass({ componentWillMount: function () { this.inputs = {}; this.model = {}; - this.registerInputs(this.props.children); }, componentDidMount: function () { @@ -61,9 +60,7 @@ Formsy.Form = React.createClass({ // update if (this.isMounted()) { - this.registerInputs(this.props.children); - - if (this.props.validationErrors) { + if (this.props.validationErrors) { this.setInputValidationErrors(this.props.validationErrors); } @@ -85,34 +82,11 @@ Formsy.Form = React.createClass({ // 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. - if (!this.props.url) { - this.updateModel(); - var model = this.mapModel(); - this.props.onSubmit(model, this.resetModel, this.updateInputsWithError); - this.state.isValid ? this.props.onValidSubmit(model, this.resetModel) : this.props.onInvalidSubmit(model, this.resetModel); - return; - } - this.updateModel(); - this.setState({ - isSubmitting: true - }); + var model = this.mapModel(); + this.props.onSubmit(model, this.resetModel, this.updateInputsWithError); + this.state.isValid ? this.props.onValidSubmit(model, this.resetModel) : this.props.onInvalidSubmit(model, this.resetModel); - this.props.onSubmit(this.mapModel(), this.resetModel, this.updateInputsWithError); - - var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers || {}; - - var method = this.props.method && utils.ajax[this.props.method.toLowerCase()] ? this.props.method.toLowerCase() : 'post'; - utils.ajax[method](this.props.url, this.mapModel(), this.props.contentType || options.contentType || 'json', headers) - .then(function (response) { - this.props.onSuccess(response); - this.props.onSubmitted(); - }.bind(this)) - .catch(this.failSubmit); }, mapModel: function () { @@ -137,14 +111,14 @@ Formsy.Form = React.createClass({ }, setInputValidationErrors: function (errors) { - Object.keys(this.inputs).forEach(function (name, index) { + Object.keys(this.inputs).forEach(function (name, index) { var component = this.inputs[name]; var args = [{ _isValid: !(name in errors), _serverError: errors[name] }]; component.setState.apply(component, args); - }.bind(this)); + }.bind(this)); }, // Go through errors from server and grab the components @@ -160,42 +134,43 @@ Formsy.Form = React.createClass({ var args = [{ _isValid: false, - _serverError: errors[name] + _validationError: errors[name] }]; component.setState.apply(component, args); }.bind(this)); }, - failSubmit: function (errors) { - this.updateInputsWithError(errors); - 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) { + traverseChildrenAndRegisterInputs: function (children) { - if (child && child.props && child.props.name) { - child.props._attachToForm = this.attachToForm; - child.props._detachFromForm = this.detachFromForm; - child.props._validate = this.validate; - child.props._isFormDisabled = this.isFormDisabled; - child.props._isValidValue = function (component, value) { - return this.runValidation(component, value).isValid; - }.bind(this); + if (typeof children !== 'object' || children === null) { + return children; + } + return React.Children.map(children, function (child) { + + if (typeof child !== 'object' || child === null) { + return child; } - if (child && child.props && child.props.children) { - this.registerInputs(child.props.children); + if (child.props && child.props.name) { + + return React.cloneElement(child, { + _attachToForm: this.attachToForm, + _detachFromForm: this.detachFromForm, + _validate: this.validate, + _isFormDisabled: this.isFormDisabled, + _isValidValue: function (component, value) { + return this.runValidation(component, value).isValid; + }.bind(this) + }, child.props && child.props.children); + } else { + return React.cloneElement(child, {}, this.traverseChildrenAndRegisterInputs(child.props && child.props.children)); } - }.bind(this)); + }, this); + }, isFormDisabled: function () { @@ -228,7 +203,7 @@ Formsy.Form = React.createClass({ // validate the input and set its state. Then check the // state of the form itself validate: function (component) { - + // Trigger onChange if (this.state.canChange) { this.props.onChange(this.getCurrentValues()); @@ -271,7 +246,7 @@ Formsy.Form = React.createClass({ if (isValid && !isRequired) { return ''; - } + } if (validationResults.errors.length) { return validationResults.errors[0]; @@ -312,57 +287,6 @@ Formsy.Form = React.createClass({ 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); - - }); - } - - return results; - - }, - -/* - - 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') { @@ -379,7 +303,9 @@ Formsy.Form = React.createClass({ results.errors.push(validation); results.failed.push(validationMethod); } else if (!validation) { - results.failed.push(validationMethod); + results.failed.push(validationMethod); + } else { + results.success.push(validationMethod); } return; @@ -391,7 +317,9 @@ Formsy.Form = React.createClass({ } return results; -*/ + + }, + // Validate the form by going through all child input components // and check their state validateForm: function () { @@ -464,9 +392,10 @@ Formsy.Form = React.createClass({ return React.DOM.form({ onSubmit: this.submit, - className: this.props.className + className: this.props.className, + novalidate: 'novalidate' in this.props ? this.props.novalidate : true }, - this.props.children + this.traverseChildrenAndRegisterInputs(this.props.children) ); } diff --git a/src/validationRules.js b/src/validationRules.js index 28f83df..05fa3b2 100644 --- a/src/validationRules.js +++ b/src/validationRules.js @@ -15,7 +15,7 @@ module.exports = { return value === ''; }, 'isEmail': function (values, value) { - return value !== undefined && 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); + return !value || 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 (values, value) { return value === true; @@ -36,13 +36,13 @@ module.exports = { } }, 'isAlpha': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z]+$/); + return !value || value.match(/^[a-zA-Z]+$/); }, 'isWords': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z\s]+$/); + return !value || value.match(/^[a-zA-Z\s]+$/); }, 'isSpecialWords': function (values, value) { - return value !== undefined && value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/); + return !value || value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/); }, isLength: function (values, value, length) { return value !== undefined && value.length === length;