diff --git a/README.md b/README.md index 768ee19..be967af 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ The main concept is that forms, inputs and validation is done very differently a ## Changes +**0.1.3**: + + - Fixed resetValue bug + **0.1.2**: - Fixed isValue check to empty string, needs to support false and 0 diff --git a/bower.json b/bower.json index f050693..9a5630b 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.1.2", + "version": "0.1.3", "main": "src/main.js", "dependencies": { "react": "^0.11.2" diff --git a/package.json b/package.json index 1ce5823..6d20fb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.1.2", + "version": "0.1.3", "description": "A form input builder and validator for React JS", "main": "src/main.js", "scripts": { diff --git a/releases/0.1.3/formsy-react-0.1.3.js b/releases/0.1.3/formsy-react-0.1.3.js new file mode 100755 index 0000000..b19f55c --- /dev/null +++ b/releases/0.1.3/formsy-react-0.1.3.js @@ -0,0 +1,348 @@ +!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); + }, + + // Detach it when component unmounts + componentWillUnmount: function () { + this.props._detachFromForm(this); + }, + + // We validate after the value has been set + setValue: function (value) { + this.setState({ + _value: value + }, function () { + this.props._validate(this); + }.bind(this)); + }, + resetValue: function () { + this.setState({ + _value: '' + }, function () { + this.props._validate(this); + }); + }, + getValue: function () { + return this.state._value; + }, + getErrorMessage: function () { + return this.isValid() || this.showRequired() ? null : this.state._serverError || this.props.validationError; + }, + isValid: function () { + return this.state._isValid; + }, + isRequired: function () { + return this.props.required; + }, + showRequired: function () { + return this.props.required && this.state._value === ''; + }, + showError: function () { + return !this.showRequired() && !this.state._isValid; + } +}; + +Formsy.addValidationRule = function (name, func) { + validationRules[name] = func; +}; + +Formsy.Form = React.createClass({ + getInitialState: function () { + return { + isValid: true, + isSubmitting: false + }; + }, + getDefaultProps: function () { + return { + onSuccess: function () {}, + onError: function () {}, + onSubmit: function () {}, + onSubmitted: function () {} + } + }, + + // Add a map to store the inputs of the form, a model to store + // the values of the form and register child inputs + componentWillMount: function () { + this.inputs = {}; + this.model = {}; + this.registerInputs(this.props.children); + }, + + componentDidMount: function () { + this.validateForm(); + }, + + // Update model, submit to url prop and send the model + submit: function (event) { + event.preventDefault(); + + if (!this.props.url) { + throw new Error('Formsy Form needs a url property to post the form'); + } + + this.updateModel(); + this.setState({ + isSubmitting: true + }); + this.props.onSubmit(); + ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json') + .then(function (response) { + this.onSuccess(response); + this.onSubmitted(); + }.bind(this)) + .catch(this.updateInputsWithError); + }, + + // Goes through all registered components and + // updates the model values + updateModel: function () { + Object.keys(this.inputs).forEach(function (name) { + var component = this.inputs[name]; + this.model[name] = component.state._value; + }.bind(this)); + }, + + // Go through errors from server and grab the components + // stored in the inputs map. Change their state to invalid + // and set the serverError message + updateInputsWithError: function (errors) { + Object.keys(errors).forEach(function (name, index) { + var component = this.inputs[name]; + var args = [{ + _isValid: false, + _serverError: errors[name] + }]; + if (index === Object.keys(errors).length - 1) { + args.push(this.validateForm); + } + component.setState.apply(component, args); + }.bind(this)); + this.setState({ + isSubmitting: false + }); + this.props.onError(errors); + this.props.onSubmitted(); + }, + + // Traverse the children and children of children to find + // all inputs by checking the name prop. Maybe do a better + // check here + registerInputs: function (children) { + React.Children.forEach(children, function (child) { + + if (child.props.name) { + child.props._attachToForm = this.attachToForm; + child.props._detachFromForm = this.detachFromForm; + child.props._validate = this.validate; + } + + if (child.props.children) { + this.registerInputs(child.props.children); + } + + }.bind(this)); + }, + + // Use the binded values and the actual input value to + // validate the input and set its state. Then check the + // state of the form itself + validate: function (component) { + + if (!component.props.validations) { + return; + } + + // Run through the validations, split them up and call + // the validator IF there is a value or it is required + var isValid = true; + if (component.props.required || component.state._value !== '') { + component.props.validations.split(',').forEach(function (validation) { + var args = validation.split(':'); + var validateMethod = args.shift(); + args = args.map(function (arg) { return JSON.parse(arg); }); + args = [component.state._value].concat(args); + if (!validationRules[validateMethod]) { + throw new Error('Formsy does not have the validation rule: ' + validateMethod); + } + if (!validationRules[validateMethod].apply(null, args)) { + isValid = false; + } + }); + } + + component.setState({ + _isValid: isValid, + _serverError: null + }, this.validateForm); + + }, + + // Validate the form by going through all child input components + // and check their state + validateForm: function () { + var allIsValid = true; + var inputs = this.inputs; + + Object.keys(inputs).forEach(function (name) { + if (!inputs[name].state._isValid) { + allIsValid = false; + } + }); + + this.setState({ + isValid: allIsValid + }); + }, + + // Method put on each input component to register + // itself to the form + attachToForm: function (component) { + this.inputs[component.props.name] = component; + this.model[component.props.name] = component.state._value; + this.validate(component); + }, + + // Method put on each input component to unregister + // itself from the form + detachFromForm: function (component) { + delete this.inputs[component.props.name]; + delete this.model[component.props.name]; + }, + render: function () { + var submitButton = React.DOM.button({ + className: this.props.submitButtonClass || options.submitButtonClass, + disabled: this.state.isSubmitting || !this.state.isValid + }, this.props.submitLabel || 'Submit'); + + var cancelButton = React.DOM.button({ + onClick: this.props.onCancel, + disabled: this.state.isSubmitting, + className: this.props.resetButtonClass || options.resetButtonClass + }, this.props.cancelLabel || 'Cancel'); + + return React.DOM.form({ + onSubmit: this.submit, + className: this.props.className + }, + this.props.children, + React.DOM.div({ + className: this.props.buttonWrapperClass || options.buttonWrapperClass + }, + this.props.showCancel || options.showCancel ? cancelButton : null, + this.props.hideSubmit || options.hideSubmit ? null : submitButton + ) + ); + + } +}); + +if (!global.exports && !global.module && (!global.define || !global.define.amd)) { + global.Formsy = Formsy; +} + +module.exports = Formsy; +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"react":"react"}]},{},["/Users/christianalfoni/Documents/dev/formsy-react/src/main.js"])("/Users/christianalfoni/Documents/dev/formsy-react/src/main.js") +}); \ No newline at end of file diff --git a/releases/0.1.3/formsy-react-0.1.3.min.js b/releases/0.1.3/formsy-react-0.1.3.min.js new file mode 100755 index 0000000..71f7ecf --- /dev/null +++ b/releases/0.1.3/formsy-react-0.1.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,s){function n(o,u){if(!i[o]){if(!e[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(r)return r(o,!0);var p=new Error("Cannot find module '"+o+"'");throw p.code="MODULE_NOT_FOUND",p}var l=i[o]={exports:{}};e[o][0].call(l.exports,function(t){var i=e[o][1][t];return n(i?i:t)},l,l.exports,t,e,i,s)}return i[o].exports}for(var r="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 s in t)o(t[s],e?e+"["+s+"]":s,i);else i.push(e+"="+encodeURIComponent(t));return i.join("&")},u=function(t,e,i,s){var s="urlencoded"===s?"application/"+s.replace("urlencoded","x-www-form-urlencoded"):"application/json";return i="application/json"===s?JSON.stringify(i):o(i),new Promise(function(n,r){try{var o=new XMLHttpRequest;o.open(t,e,!0),o.setRequestHeader("Accept","application/json"),o.setRequestHeader("Content-Type",s),o.onreadystatechange=function(){4===o.readyState&&(o.status>=200&&o.status<300?n(o.responseText?JSON.parse(o.responseText):null):r(o.responseText?JSON.parse(o.responseText):null))},o.send(i)}catch(u){r(u)}})},a={post:u.bind(null,"POST"),put:u.bind(null,"PUT")},p={};n.defaults=function(t){p=t},n.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)},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},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}},n.addValidationRule=function(t,e){r[t]=e},n.Form=s.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 s=this.inputs[e],n=[{_isValid:!1,_serverError:t[e]}];i===Object.keys(t).length-1&&n.push(this.validateForm),s.setState.apply(s,n)}.bind(this)),this.setState({isSubmitting:!1}),this.props.onError(t),this.props.onSubmitted()},registerInputs:function(t){s.Children.forEach(t,function(t){t.props.name&&(t.props._attachToForm=this.attachToForm,t.props._detachFromForm=this.detachFromForm,t.props._validate=this.validate),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 s=i.split(":"),n=s.shift();if(s=s.map(function(t){return JSON.parse(t)}),s=[t.state._value].concat(s),!r[n])throw new Error("Formsy does not have the validation rule: "+n);r[n].apply(null,s)||(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=s.DOM.button({className:this.props.submitButtonClass||p.submitButtonClass,disabled:this.state.isSubmitting||!this.state.isValid},this.props.submitLabel||"Submit"),e=s.DOM.button({onClick:this.props.onCancel,disabled:this.state.isSubmitting,className:this.props.resetButtonClass||p.resetButtonClass},this.props.cancelLabel||"Cancel");return s.DOM.form({onSubmit:this.submit,className:this.props.className},this.props.children,s.DOM.div({className:this.props.buttonWrapperClass||p.buttonWrapperClass},this.props.showCancel||p.showCancel?e:null,this.props.hideSubmit||p.hideSubmit?null:t))}}),i.exports||i.module||i.define&&i.define.amd||(i.Formsy=n),e.exports=n}).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 e3df5d2..8d2f81c 100644 --- a/src/main.js +++ b/src/main.js @@ -116,6 +116,8 @@ Formsy.Mixin = { resetValue: function () { this.setState({ _value: '' + }, function () { + this.props._validate(this); }); }, getValue: function () {