From 014f7add09416bc64ccb7f6a626ee813e0fa1b00 Mon Sep 17 00:00:00 2001 From: Christian Alfoni Date: Tue, 28 Oct 2014 12:56:35 +0100 Subject: [PATCH] Added hasValue() method --- README.md | 23 ++ bower.json | 2 +- package.json | 2 +- releases/0.2.0/formsy-react-0.2.0.js | 351 +++++++++++++++++++++++ releases/0.2.0/formsy-react-0.2.0.min.js | 1 + src/main.js | 3 + 6 files changed, 380 insertions(+), 2 deletions(-) create mode 100755 releases/0.2.0/formsy-react-0.2.0.js create mode 100755 releases/0.2.0/formsy-react-0.2.0.min.js diff --git a/README.md b/README.md index be967af..bc3a6e9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ A form input builder and validator for React JS - [required](#required) - [getValue()](#getvalue) - [setValue()](#setvalue) + - [hasValue()](#hasvalue) - [resetValue()](#resetvalue) - [getErrorMessage()](#geterrormessage) - [isValid()](#isvalid) @@ -64,6 +65,10 @@ The main concept is that forms, inputs and validation is done very differently a ## Changes +**0.2.0**: + + - Implemented hasValue() method + **0.1.3**: - Fixed resetValue bug @@ -298,6 +303,24 @@ var MyInput = React.createClass({ ``` Sets the value of your form input component. Notice that it does not have to be a text input. Anything can set a value on the component. Think calendars, checkboxes, autocomplete stuff etc. +#### hasValue() +```javascript +var MyInput = React.createClass({ + changeValue: function (event) { + this.setValue(event.currentTarget.value); + }, + render: function () { + return ( +
+ + {this.hasValue() ? 'There is a value here' : 'No value entered yet'} +
+ ); + } +}); +``` +The hasValue() method helps you identify if there actually is a value or not. The only invalid value in Formsy is an empty string, "". All other values are valid as they could be something you want to send to the server. F.ex. the number zero (0), or false. + #### resetValue() ```javascript var MyInput = React.createClass({ diff --git a/bower.json b/bower.json index 9a5630b..7c224c3 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.1.3", + "version": "0.2.0", "main": "src/main.js", "dependencies": { "react": "^0.11.2" diff --git a/package.json b/package.json index 6d20fb3..a822830 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formsy-react", - "version": "0.1.3", + "version": "0.2.0", "description": "A form input builder and validator for React JS", "main": "src/main.js", "scripts": { diff --git a/releases/0.2.0/formsy-react-0.2.0.js b/releases/0.2.0/formsy-react-0.2.0.js new file mode 100755 index 0000000..0594b6a --- /dev/null +++ b/releases/0.2.0/formsy-react-0.2.0.js @@ -0,0 +1,351 @@ +!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; + }, + 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.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.2.0/formsy-react-0.2.0.min.js b/releases/0.2.0/formsy-react-0.2.0.min.js new file mode 100755 index 0000000..4c3fb00 --- /dev/null +++ b/releases/0.2.0/formsy-react-0.2.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,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},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}},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 8d2f81c..ca360ed 100644 --- a/src/main.js +++ b/src/main.js @@ -123,6 +123,9 @@ Formsy.Mixin = { 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; },