Release 0.4.0
This commit is contained in:
parent
dc6ac20285
commit
b04c989cf6
16
README.md
16
README.md
|
|
@ -63,6 +63,10 @@ The main concept is that forms, inputs and validation is done very differently a
|
|||
|
||||
## <a name="changes">Changes</a>
|
||||
|
||||
**0.4.0**:
|
||||
- Possibility to handle form data manually using "onSubmit"
|
||||
- Added two more default rules. *isWords* and *isSpecialWords*
|
||||
|
||||
**0.3.0**:
|
||||
- Deprecated everything related to buttons automatically added
|
||||
- Added onValid and onInvalid handlers, use those to manipulate submit buttons etc.
|
||||
|
|
@ -197,7 +201,7 @@ Sets a class name on the form itself.
|
|||
```html
|
||||
<Formsy.Form url="/users"></Formsy.Form>
|
||||
```
|
||||
Will either **POST** or **PUT** to the url specified when submitted.
|
||||
Will either **POST** or **PUT** to the url specified when submitted. If you do not pass a url the data for the form will be passed to the **onSubmit** handler.
|
||||
|
||||
#### <a name="method">method</a>
|
||||
```html
|
||||
|
|
@ -219,11 +223,13 @@ Supports **json** (default) and **urlencoded** (x-www-form-urlencoded).
|
|||
```
|
||||
Takes a function to run when the server has responded with a success http status code.
|
||||
|
||||
#### <a name="onsubmit">onSubmit()</a>
|
||||
#### <a name="onsubmit">onSubmit(data, resetForm)</a>
|
||||
```html
|
||||
<Formsy.Form url="/users" onSubmit={this.showFormLoader}></Formsy.Form>
|
||||
```
|
||||
Takes a function to run when the submit button has been clicked.
|
||||
Takes a function to run when the submit button has been clicked. The first argument is the data of the form. The second argument will reset the form.
|
||||
|
||||
**note!** When resetting the form the form elements needs to bind its current value using the *getValue* method. That will empty for example an input.
|
||||
|
||||
#### <a name="onsubmitted">onSubmitted()</a>
|
||||
```html
|
||||
|
|
@ -503,9 +509,9 @@ Returns true if string is only letters
|
|||
```
|
||||
Returns true if string is only letters, including spaces and tabs
|
||||
|
||||
**isWordsSpecial**
|
||||
**isSpecialWords**
|
||||
```html
|
||||
<MyInputComponent name="foo" validations="isWordsSpecial"/>
|
||||
<MyInputComponent name="foo" validations="isSpecialWords"/>
|
||||
```
|
||||
Returns true if string is only letters, including special letters (a-z,ú,ø,æ,å)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "formsy-react",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "A form input builder and validator for React JS",
|
||||
"main": "src/main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,381 @@
|
|||
!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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
(function (global){
|
||||
var React = global.React || require('react');
|
||||
var Formsy = {};
|
||||
var validationRules = {
|
||||
'isValue': function (value) {
|
||||
return value !== '';
|
||||
},
|
||||
'isEmail': function (value) {
|
||||
return 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 (value) {
|
||||
return value === true;
|
||||
},
|
||||
'isNumeric': function (value) {
|
||||
return value.match(/^-?[0-9]+$/)
|
||||
},
|
||||
'isAlpha': function (value) {
|
||||
return value.match(/^[a-zA-Z]+$/);
|
||||
},
|
||||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
if (max !== undefined) {
|
||||
return value.length >= 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, headers) {
|
||||
|
||||
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);
|
||||
|
||||
// Add passed headers
|
||||
Object.keys(headers).forEach(function (header) {
|
||||
xhr.setRequestHeader(header, headers[header]);
|
||||
});
|
||||
|
||||
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 {
|
||||
headers: {},
|
||||
onSuccess: function () {},
|
||||
onError: function () {},
|
||||
onSubmit: function () {},
|
||||
onSubmitted: function () {},
|
||||
onValid: function () {},
|
||||
onInvalid: 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();
|
||||
|
||||
// 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();
|
||||
this.props.onSubmit(this.model, this.resetModel);
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateModel();
|
||||
this.setState({
|
||||
isSubmitting: true
|
||||
});
|
||||
|
||||
this.props.onSubmit();
|
||||
|
||||
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();
|
||||
}.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));
|
||||
},
|
||||
|
||||
// Reset each key in the model to the original / initial value
|
||||
resetModel: function() {
|
||||
Object.keys(this.inputs).forEach(function (name) {
|
||||
this.inputs[name].resetValue();
|
||||
}.bind(this));
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
allIsValid && this.props.onValid();
|
||||
!allIsValid && this.props.onInvalid();
|
||||
},
|
||||
|
||||
// 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 () {
|
||||
|
||||
return React.DOM.form({
|
||||
onSubmit: this.submit,
|
||||
className: this.props.className
|
||||
},
|
||||
this.props.children
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
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"}]},{},[1])(1)
|
||||
});
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -19,7 +19,7 @@ var validationRules = {
|
|||
'isWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s]+$/);
|
||||
},
|
||||
'isWordsSpecial': function (value) {
|
||||
'isSpecialWords': function (value) {
|
||||
return value.match(/^[a-zA-Z\s\u00C0-\u017F]+$/);
|
||||
},
|
||||
isLength: function (value, min, max) {
|
||||
|
|
@ -219,7 +219,7 @@ Formsy.Form = React.createClass({
|
|||
this.props.onSubmit();
|
||||
|
||||
var headers = (Object.keys(this.props.headers).length && this.props.headers) || options.headers;
|
||||
console.log('headers', headers);
|
||||
|
||||
ajax[this.props.method || 'post'](this.props.url, this.model, this.props.contentType || options.contentType || 'json', headers)
|
||||
.then(function (response) {
|
||||
this.onSuccess(response);
|
||||
|
|
@ -242,6 +242,7 @@ Formsy.Form = React.createClass({
|
|||
Object.keys(this.inputs).forEach(function (name) {
|
||||
this.inputs[name].resetValue();
|
||||
}.bind(this));
|
||||
this.validateForm();
|
||||
},
|
||||
|
||||
// Go through errors from server and grab the components
|
||||
|
|
|
|||
Loading…
Reference in New Issue