formsy-react/release/formsy-react.js

635 lines
19 KiB
JavaScript

(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 = require('./validationRules.js');
var utils = require('./utils.js');
var Mixin = require('./Mixin.js');
var options = {};
Formsy.Mixin = Mixin;
Formsy.defaults = function (passedOptions) {
options = passedOptions;
};
Formsy.addValidationRule = function (name, func) {
validationRules[name] = func;
};
Formsy.Form = React.createClass({displayName: "Form",
getInitialState: function () {
return {
isValid: true,
isSubmitting: false,
canChange: false
};
},
getDefaultProps: function () {
return {
headers: {},
onSuccess: function () {},
onError: function () {},
onSubmit: function () {},
onValidSubmit: function () {},
onInvalidSubmit: function () {},
onSubmitted: function () {},
onValid: function () {},
onInvalid: function () {},
onChange: 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();
},
componentWillUpdate: function () {
var inputKeys = Object.keys(this.inputs);
// The updated children array is not available here for some reason,
// we need to wait for next event loop
setTimeout(function () {
// The component might have been unmounted on an
// update
if (this.isMounted()) {
this.registerInputs(this.props.children);
if (this.props.validationErrors) {
this.setInputValidationErrors(this.props.validationErrors);
}
var newInputKeys = Object.keys(this.inputs);
if (utils.arraysDiffer(inputKeys, newInputKeys)) {
this.validateForm();
}
}
}.bind(this), 0);
},
// Update model, submit to url prop and send the model
submit: function (event) {
event.preventDefault();
// Trigger form as not pristine.
// 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
});
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 () {
return this.props.mapping ? this.props.mapping(this.model) : this.model;
},
// 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();
},
setInputValidationErrors: function (errors) {
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));
},
// 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];
if (!component) {
throw new Error('You are trying to update an input that does not exists. Verify errors object with input names. ' + JSON.stringify(errors));
}
var args = [{
_isValid: false,
_serverError: 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) {
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 = this.runValidation;
}
if (child && child.props && child.props.children) {
this.registerInputs(child.props.children);
}
}.bind(this));
},
isFormDisabled: function () {
return this.props.disabled;
},
getCurrentValues: function () {
return Object.keys(this.inputs).reduce(function (data, name) {
var component = this.inputs[name];
data[name] = component.state._value;
return data;
}.bind(this), {});
},
setFormPristine: function (isPristine) {
var inputs = this.inputs;
var inputKeys = Object.keys(inputs);
// Iterate through each component and set it as pristine
// or "dirty".
inputKeys.forEach(function (name, index) {
var component = inputs[name];
component.setState({
_isPristine: isPristine
});
}.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) {
// Trigger onChange
if (this.state.canChange) {
this.props.onChange(this.getCurrentValues());
}
if (!component.props.required && !component._validations) {
return;
}
// Run through the validations, split them up and call
// the validator IF there is a value or it is required
var isValid = this.runValidation(component);
component.setState({
_isValid: isValid,
_serverError: null
}, this.validateForm);
},
// Checks validation on current value or a passed value
runValidation: function (component, value) {
var isValid = true;
value = arguments.length === 2 ? value : component.state._value;
if (component._validations.length && (component.props.required || value !== '')) {
component._validations.split(/\,(?![^{\[]*[}\]])/g).forEach(function (validation) {
var args = validation.split(':');
var validateMethod = args.shift();
args = args.map(function (arg) {
try {
return JSON.parse(arg);
} catch (e) {
return arg; // It is a string if it can not parse it
}
});
args = [value].concat(args);
if (!validationRules[validateMethod]) {
throw new Error('Formsy does not have the validation rule: ' + validateMethod);
}
if (!validationRules[validateMethod].apply(this.getCurrentValues(), args)) {
isValid = false;
}
}.bind(this));
}
return isValid;
},
// Validate the form by going through all child input components
// and check their state
validateForm: function () {
var allIsValid = true;
var inputs = this.inputs;
var inputKeys = Object.keys(inputs);
// We need a callback as we are validating all inputs again. This will
// run when the last component has set its state
var onValidationComplete = function () {
inputKeys.forEach(function (name) {
if (!inputs[name].state._isValid) {
allIsValid = false;
}
}.bind(this));
this.setState({
isValid: allIsValid
});
if (allIsValid) {
this.props.onValid();
} else {
this.props.onInvalid();
}
// Tell the form that it can start to trigger change events
this.setState({
canChange: true
});
}.bind(this);
// Run validation again in case affected by other inputs. The
// last component validated will run the onValidationComplete callback
inputKeys.forEach(function (name, index) {
var component = inputs[name];
var isValid = this.runValidation(component);
component.setState({
_isValid: isValid,
_serverError: null
}, index === inputKeys.length - 1 ? onValidationComplete : null);
}.bind(this));
// If there are no inputs, set state where form is ready to trigger
// change event. New inputs might be added later
if (!inputKeys.length && this.isMounted()) {
this.setState({
canChange: true
});
}
},
// 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 : {})
},{"./Mixin.js":2,"./utils.js":3,"./validationRules.js":4,"react":"react"}],2:[function(require,module,exports){
module.exports = {
getInitialState: function () {
var value = 'value' in this.props ? this.props.value : '';
return {
_value: value,
_isValid: true,
_isPristine: true,
_pristineValue: value
};
},
componentWillMount: function () {
var configure = 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');
}
if (!this.props._attachToForm) {
return setTimeout(function () {
if (!this.isMounted()) return;
if (!this.props._attachToForm) {
throw new Error('Form Mixin requires component to be nested in a Form');
}
configure();
}.bind(this), 0);
}
configure();
},
// 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;
this.setValidations(nextProps.validations, nextProps.required);
},
componentDidUpdate: function (prevProps, prevState) {
var isValueChanged = function () {
return (
this.props.value !== prevProps.value && (
this.state._value === prevProps.value ||
// Since undefined is converted to empty string we have to
// check that specifically
(this.state._value === '' && prevProps.value === undefined)
)
);
}.bind(this);
// If validations has changed or something outside changes
// the value, set the value again running a validation
if (prevProps.validations !== this.props.validations || isValueChanged()) {
var value = 'value' in this.props ? this.props.value : '';
this.setValue(value);
}
},
// Detach it when component unmounts
componentWillUnmount: function () {
this.props._detachFromForm(this);
},
setValidations: function (validations, required) {
// Add validations to the store itself as the props object can not be modified
this._validations = validations || '';
if (required) {
this._validations = validations ? validations + ',' : '';
this._validations += 'isValue';
}
},
// We validate after the value has been set
setValue: function (value) {
this.setState({
_value: value,
_isPristine: false
}, function () {
this.props._validate(this);
}.bind(this));
},
resetValue: function () {
this.setState({
_value: this.state._pristineValue,
_isPristine: true
}, 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;
},
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.isRequired() && this.state._value === '';
},
showError: function () {
return !this.showRequired() && !this.state._isValid;
},
isValidValue: function (value) {
return this.props._isValidValue.call(null, this, value);
}
};
},{}],3:[function(require,module,exports){
var csrfTokenSelector = typeof document != 'undefined' ? document.querySelector('meta[name="csrf-token"]') : null;
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);
if (!!csrfTokenSelector && !!csrfTokenSelector.content) {
xhr.setRequestHeader('X-CSRF-Token', csrfTokenSelector.content);
}
// Add passed headers
Object.keys(headers).forEach(function (header) {
xhr.setRequestHeader(header, headers[header]);
});
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
try {
var response = xhr.responseText ? JSON.parse(xhr.responseText) : null;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(response);
} else {
reject(response);
}
} catch (e) {
reject(e);
}
}
};
xhr.send(data);
} catch (e) {
reject(e);
}
});
};
module.exports = {
arraysDiffer: function (arrayA, arrayB) {
var isDifferent = false;
if (arrayA.length !== arrayB.length) {
isDifferent = true;
} else {
arrayA.forEach(function (item, index) {
if (item !== arrayB[index]) {
isDifferent = true;
}
});
}
return isDifferent;
},
ajax: {
post: request.bind(null, 'POST'),
put: request.bind(null, 'PUT')
}
};
},{}],4:[function(require,module,exports){
module.exports = {
'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) {
if (typeof value === 'number') {
return true;
} else {
var matchResults = value.match(/[-+]?(\d*[.])?\d+/);
if (!! matchResults) {
return matchResults[0] == value;
} else {
return false;
}
}
},
'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;
},
equalsField: function (value, field) {
return value == this[field];
}
};
},{}]},{},[1]);