diff --git a/README.md b/README.md
index 2bc1ee1..eb97de8 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,10 @@ The main concept is that forms, inputs and validation is done very differently a
## Changes
+**0.2.3**:
+
+ - Fixed bug where child does not have props property
+
**0.2.2**:
- Fixed bug with updating the props
diff --git a/bower.json b/bower.json
index 889c1b9..fcd170f 100644
--- a/bower.json
+++ b/bower.json
@@ -1,6 +1,6 @@
{
"name": "formsy-react",
- "version": "0.2.2",
+ "version": "0.2.3",
"main": "src/main.js",
"dependencies": {
"react": "^0.11.2"
diff --git a/package.json b/package.json
index 9dd3c89..75e480c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "formsy-react",
- "version": "0.2.2",
+ "version": "0.2.3",
"description": "A form input builder and validator for React JS",
"main": "src/main.js",
"scripts": {
diff --git a/releases/0.2.3/formsy-react-0.2.3.js b/releases/0.2.3/formsy-react-0.2.3.js
new file mode 100755
index 0000000..79feb3f
--- /dev/null
+++ b/releases/0.2.3/formsy-react-0.2.3.js
@@ -0,0 +1,358 @@
+!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) {
+
+ 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);
+ },
+
+ // 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 {
+ 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 && 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
+ });
+ },
+
+ // 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")
+});
\ No newline at end of file
diff --git a/releases/0.2.3/formsy-react-0.2.3.min.js b/releases/0.2.3/formsy-react-0.2.3.min.js
new file mode 100755
index 0000000..8f21743
--- /dev/null
+++ b/releases/0.2.3/formsy-react-0.2.3.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 s(r,u){if(!i[r]){if(!e[r]){var a="function"==typeof require&&require;if(!u&&a)return a(r,!0);if(o)return o(r,!0);var p=new Error("Cannot find module '"+r+"'");throw p.code="MODULE_NOT_FOUND",p}var l=i[r]={exports:{}};e[r][0].call(l.exports,function(t){var i=e[r][1][t];return s(i?i:t)},l,l.exports,t,e,i,n)}return i[r].exports}for(var o="function"==typeof require&&require,r=0;r=e&&t.length<=i:t.length>=e},equals:function(t,e){return t==e}},r=function(t,e,i){var i=i||[];if("object"==typeof t)for(var n in t)r(t[n],e?e+"["+n+"]":n,i);else i.push(e+"="+encodeURIComponent(t));return i.join("&")},u=function(t,e,i,n){var n="urlencoded"===n?"application/"+n.replace("urlencoded","x-www-form-urlencoded"):"application/json";return i="application/json"===n?JSON.stringify(i):r(i),new Promise(function(s,o){try{var r=new XMLHttpRequest;r.open(t,e,!0),r.setRequestHeader("Accept","application/json"),r.setRequestHeader("Content-Type",n),r.onreadystatechange=function(){4===r.readyState&&(r.status>=200&&r.status<300?s(r.responseText?JSON.parse(r.responseText):null):o(r.responseText?JSON.parse(r.responseText):null))},r.send(i)}catch(u){o(u)}})},a={post:u.bind(null,"POST"),put:u.bind(null,"PUT")},p={};s.defaults=function(t){p=t},s.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}},s.addValidationRule=function(t,e){o[t]=e},s.Form=n.createClass({getInitialState:function(){return{isValid:!0,isSubmitting:!1}},getDefaultProps:function(){return{onSuccess:function(){},onError:function(){},onSubmit:function(){},onSubmitted: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(),a[this.props.method||"post"](this.props.url,this.model,this.props.contentType||p.contentType||"json").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],s=[{_isValid:!1,_serverError:t[e]}];i===Object.keys(t).length-1&&s.push(this.validateForm),n.setState.apply(n,s)}.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(":"),s=n.shift();if(n=n.map(function(t){return JSON.parse(t)}),n=[t.state._value].concat(n),!o[s])throw new Error("Formsy does not have the validation rule: "+s);o[s].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})},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(){var t=n.DOM.button({className:this.props.submitButtonClass||p.submitButtonClass,disabled:this.state.isSubmitting||!this.state.isValid},this.props.submitLabel||"Submit"),e=n.DOM.button({onClick:this.props.onCancel,disabled:this.state.isSubmitting,className:this.props.cancelButtonClass||p.cancelButtonClass},this.props.cancelLabel||"Cancel");return n.DOM.form({onSubmit:this.submit,className:this.props.className},this.props.children,n.DOM.div({className:this.props.buttonWrapperClass||p.buttonWrapperClass},this.props.onCancel?e:null,this.props.hideSubmit||p.hideSubmit?null:t))}}),i.exports||i.module||i.define&&i.define.amd||(i.Formsy=s),e.exports=s}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{react:"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js")});
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
index 109958a..95455bc 100644
--- a/src/main.js
+++ b/src/main.js
@@ -240,13 +240,13 @@ Formsy.Form = React.createClass({
registerInputs: function (children) {
React.Children.forEach(children, function (child) {
- if (child.props.name) {
+ if (child.props && child.props.name) {
child.props._attachToForm = this.attachToForm;
child.props._detachFromForm = this.detachFromForm;
child.props._validate = this.validate;
}
- if (child.props.children) {
+ if (child.props && child.props.children) {
this.registerInputs(child.props.children);
}