1
0
mirror of https://github.com/ascribe/onion.git synced 2024-11-15 09:35:10 +01:00
onion/js/components/ascribe_forms/form.js

298 lines
8.9 KiB
JavaScript
Raw Normal View History

2015-06-23 16:02:48 +02:00
'use strict';
import React from 'react';
import ReactAddons from 'react/addons';
import Button from 'react-bootstrap/lib/Button';
import AlertDismissable from './alert';
2015-06-23 16:02:48 +02:00
2015-08-10 14:15:32 +02:00
import GlobalNotificationModel from '../../models/global_notification_model';
import GlobalNotificationActions from '../../actions/global_notification_actions';
2015-06-23 16:02:48 +02:00
import requests from '../../utils/requests';
import { getLangText } from '../../utils/lang_utils';
2015-07-10 18:51:35 +02:00
import { mergeOptionsWithDuplicates } from '../../utils/general_utils';
2015-06-23 16:02:48 +02:00
2015-06-23 16:02:48 +02:00
let Form = React.createClass({
propTypes: {
url: React.PropTypes.string,
2015-08-06 10:09:25 +02:00
method: React.PropTypes.string,
2015-08-05 14:40:26 +02:00
buttonSubmitText: React.PropTypes.string,
2015-06-23 16:02:48 +02:00
handleSuccess: React.PropTypes.func,
getFormData: React.PropTypes.func,
children: React.PropTypes.oneOfType([
React.PropTypes.object,
React.PropTypes.array
2015-07-08 14:37:20 +02:00
]),
2015-08-06 10:09:25 +02:00
className: React.PropTypes.string,
spinner: React.PropTypes.element,
buttons: React.PropTypes.oneOfType([
React.PropTypes.element,
React.PropTypes.arrayOf(React.PropTypes.element)
2015-08-10 14:15:32 +02:00
]),
// Can be used to freeze the whole form
disabled: React.PropTypes.bool,
2015-08-10 14:15:32 +02:00
// You can use the form for inline requests, like the submit click on a button.
// For the form to then not display the error on top, you need to enable this option.
// It will make use of the GlobalNotification
isInline: React.PropTypes.bool,
autoComplete: React.PropTypes.string,
onReset: React.PropTypes.func
2015-08-06 10:09:25 +02:00
},
getDefaultProps() {
return {
method: 'post',
buttonSubmitText: 'SAVE',
autoComplete: 'off'
2015-08-06 10:09:25 +02:00
};
2015-06-23 16:02:48 +02:00
},
getInitialState() {
return {
edited: false,
submitted: false,
errors: []
};
},
2015-08-06 10:09:25 +02:00
2015-09-10 10:51:47 +02:00
reset() {
// If onReset prop is defined from outside,
// notify component that a form reset is happening.
if(typeof this.props.onReset === 'function') {
this.props.onReset();
}
2015-09-10 10:51:47 +02:00
for(let ref in this.refs) {
if(typeof this.refs[ref].reset === 'function') {
2015-06-23 16:02:48 +02:00
this.refs[ref].reset();
}
}
2015-07-10 10:30:17 +02:00
this.setState(this.getInitialState());
2015-06-23 16:02:48 +02:00
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
submit(event){
2015-08-06 10:09:25 +02:00
if(event) {
2015-06-23 16:02:48 +02:00
event.preventDefault();
}
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
this.setState({submitted: true});
this.clearErrors();
2015-08-06 10:09:25 +02:00
// selecting http method based on props
if(this[this.props.method] && typeof this[this.props.method] === 'function') {
2015-08-06 10:09:25 +02:00
window.setTimeout(() => this[this.props.method](), 100);
} else {
throw new Error('This HTTP method is not supported by form.js (' + this.props.method + ')');
}
2015-06-23 16:02:48 +02:00
},
2015-08-06 10:09:25 +02:00
post() {
2015-06-23 16:02:48 +02:00
requests
.post(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
put() {
requests
.put(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
patch() {
requests
.patch(this.props.url, { body: this.getFormData() })
.then(this.handleSuccess)
.catch(this.handleError);
},
2015-08-06 10:09:25 +02:00
delete() {
requests
.delete(this.props.url, this.getFormData())
.then(this.handleSuccess)
.catch(this.handleError);
},
2015-09-10 10:51:47 +02:00
getFormData() {
2015-07-10 11:59:03 +02:00
let data = {};
for(let ref in this.refs) {
2015-06-23 16:02:48 +02:00
data[this.refs[ref].props.name] = this.refs[ref].state.value;
}
if(typeof this.props.getFormData === 'function') {
2015-07-10 18:51:35 +02:00
data = mergeOptionsWithDuplicates(data, this.props.getFormData());
}
2015-06-23 16:02:48 +02:00
return data;
},
handleChangeChild(){
this.setState({ edited: true });
2015-06-23 16:02:48 +02:00
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
handleSuccess(response){
if(typeof this.props.handleSuccess === 'function') {
2015-06-23 16:02:48 +02:00
this.props.handleSuccess(response);
}
for(let ref in this.refs) {
if(this.refs[ref] && typeof this.refs[ref].handleSuccess === 'function'){
2015-06-23 16:02:48 +02:00
this.refs[ref].handleSuccess();
}
}
2015-08-06 10:09:25 +02:00
this.setState({
edited: false,
submitted: false
});
2015-06-23 16:02:48 +02:00
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
handleError(err){
if (err.json) {
for (let input in err.json.errors){
2015-06-23 16:02:48 +02:00
if (this.refs && this.refs[input] && this.refs[input].state) {
2015-09-10 10:51:47 +02:00
this.refs[input].setErrors(err.json.errors[input]);
2015-06-23 16:02:48 +02:00
} else {
this.setState({errors: this.state.errors.concat(err.json.errors[input])});
}
}
} else {
let formData = this.getFormData();
// sentry shouldn't post the user's password
if(formData.password) {
delete formData.password;
}
console.logGlobal(err, false, formData);
2015-08-10 14:15:32 +02:00
if(this.props.isInline) {
let notification = new GlobalNotificationModel(getLangText('Something went wrong, please try again later'), 'danger');
GlobalNotificationActions.appendGlobalNotification(notification);
} else {
this.setState({errors: [getLangText('Something went wrong, please try again later')]});
}
2015-06-23 16:02:48 +02:00
}
this.setState({submitted: false});
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
clearErrors(){
for(let ref in this.refs){
if (this.refs[ref] && typeof this.refs[ref].clearErrors === 'function'){
2015-06-23 16:02:48 +02:00
this.refs[ref].clearErrors();
}
}
this.setState({errors: []});
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
getButtons() {
if (this.state.submitted){
2015-06-23 16:02:48 +02:00
return this.props.spinner;
}
if (this.props.buttons){
return this.props.buttons;
2015-06-23 16:02:48 +02:00
}
let buttons = null;
2015-06-23 16:02:48 +02:00
2015-09-21 14:22:52 +02:00
if (this.state.edited && !this.props.disabled){
2015-06-23 16:02:48 +02:00
buttons = (
<div className="row" style={{margin: 0}}>
<p className="pull-right">
<Button
className="btn btn-default btn-sm ascribe-margin-1px"
type="submit">
{this.props.buttonSubmitText}
</Button>
<Button
className="btn btn-danger btn-delete btn-sm ascribe-margin-1px"
type="reset">
CANCEL
</Button>
2015-06-23 16:02:48 +02:00
</p>
</div>
);
}
return buttons;
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
getErrors() {
let errors = null;
if (this.state.errors.length > 0){
errors = this.state.errors.map((error) => {
return <AlertDismissable error={error} key={error}/>;
});
}
return errors;
},
2015-08-06 10:09:25 +02:00
2015-06-23 16:02:48 +02:00
renderChildren() {
return ReactAddons.Children.map(this.props.children, (child) => {
2015-06-30 10:42:58 +02:00
if (child) {
return ReactAddons.addons.cloneWithProps(child, {
handleChange: this.handleChangeChild,
ref: child.props.name,
// We need this in order to make editable be overridable when setting it directly
// on Property
2015-08-20 11:03:00 +02:00
editable: child.props.overrideForm ? child.props.editable : !this.props.disabled
2015-06-30 10:42:58 +02:00
});
}
2015-06-23 16:02:48 +02:00
});
},
2015-08-06 10:09:25 +02:00
/**
* All webkit-based browsers are ignoring the attribute autoComplete="off",
* as stated here: http://stackoverflow.com/questions/15738259/disabling-chrome-autofill/15917221#15917221
* So what we actually have to do is depended on whether or not this.props.autoComplete is set to "on" or "off"
* insert two fake hidden inputs that mock password and username so that chrome/safari is filling those
*/
getFakeAutocompletableInputs() {
if(this.props.autoComplete === 'off') {
return (
<span>
<input style={{display: 'none'}} type="text" name="fakeusernameremembered"/>
<input style={{display: 'none'}} type="password" name="fakepasswordremembered"/>
</span>
);
} else {
return null;
}
},
2015-06-23 16:02:48 +02:00
render() {
2015-07-08 14:37:20 +02:00
let className = 'ascribe-form';
if(this.props.className) {
className += ' ' + this.props.className;
}
2015-06-23 16:02:48 +02:00
return (
2015-06-30 10:42:58 +02:00
<form
role="form"
2015-07-08 14:37:20 +02:00
className={className}
onSubmit={this.submit}
2015-09-10 10:51:47 +02:00
onReset={this.reset}
autoComplete={this.props.autoComplete}>
{this.getFakeAutocompletableInputs()}
2015-06-23 16:02:48 +02:00
{this.getErrors()}
{this.renderChildren()}
{this.getButtons()}
</form>
);
}
});
2015-07-15 18:55:02 +02:00
export default Form;