Vulcan/packages/nova-forms/lib/NovaForm.jsx

294 lines
8.8 KiB
React
Raw Normal View History

import React, { PropTypes, Component } from 'react';
import Formsy from 'formsy-react';
import { Button } from 'react-bootstrap';
import FormComponent from "./FormComponent.jsx";
import Utils from './utils.js';
2016-04-07 15:29:02 +09:00
/*
1. Constructor
2. Helpers
3. Errors
4. Context
4. Method & Callback
5. Render
*/
class NovaForm extends Component{
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ----------------------------- Constructor --------------------------- //
// --------------------------------------------------------------------- //
2016-04-04 16:50:07 +09:00
constructor(props) {
super(props);
this.submitForm = this.submitForm.bind(this);
2016-04-07 22:21:01 +09:00
this.updateState = this.updateState.bind(this);
this.methodCallback = this.methodCallback.bind(this);
2016-04-07 12:43:41 +09:00
this.addToPrefilledValues = this.addToPrefilledValues.bind(this);
2016-04-04 16:50:07 +09:00
this.throwError = this.throwError.bind(this);
this.clearErrors = this.clearErrors.bind(this);
this.state = {
disabled: false,
2016-04-07 22:21:01 +09:00
errors: [],
prefilledValues: {},
currentValues: {}
};
}
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Helpers ----------------------------- //
// --------------------------------------------------------------------- //
2016-04-04 16:50:07 +09:00
// if a document is being passed, this is an edit form
getFormType() {
return this.props.document ? "edit" : "new";
}
2016-04-04 16:50:07 +09:00
// get relevant fields
2016-04-07 15:24:38 +09:00
getFieldNames() {
const collection = this.props.collection;
2016-04-07 22:21:01 +09:00
const fields = this.getFormType() === "edit" ? collection.getEditableFields(this.props.currentUser, this.getDocument()) : collection.getInsertableFields(this.props.currentUser);
return fields;
}
2016-04-07 22:21:01 +09:00
// look in the document, prefilled values, or inputted values
getDocument() {
const document = Object.assign(this.props.document || {}, this.state.prefilledValues, this.state.currentValues);
return document;
}
// whenever the form changes, update its state
updateState(e) {
// e can sometimes be event, sometims be currentValue
// see https://github.com/christianalfoni/formsy-react/issues/203
if (e.stopPropagation) {
e.stopPropagation();
} else {
// get rid of empty fields
_.forEach(e, (value, key) => {
if (_.isEmpty(value)) {
delete e[key];
}
});
this.setState({
currentValues: e
});
}
}
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Errors ------------------------------ //
// --------------------------------------------------------------------- //
2016-04-04 16:50:07 +09:00
// clear all errors
clearErrors() {
this.setState({
errors: []
});
}
// render errors
renderErrors() {
Flash = Telescope.components.Flash;
return <div className="form-errors">{this.state.errors.map(message => <Flash key={message} message={message}/>)}</div>
}
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Context ----------------------------- //
// --------------------------------------------------------------------- //
// add error to state
throwError(error) {
this.setState({
errors: [error]
});
}
2016-04-07 15:24:38 +09:00
// add something to prefilled values
2016-04-07 12:43:41 +09:00
addToPrefilledValues(property) {
this.setState({
prefilledValues: {...this.state.prefilledValues, ...property}
});
}
2016-04-07 15:24:38 +09:00
// pass on context to all child components
2016-04-04 16:50:07 +09:00
getChildContext() {
return {
throwError: this.throwError,
2016-04-07 12:43:41 +09:00
prefilledValues: this.state.prefilledValues,
addToPrefilledValues: this.addToPrefilledValues
2016-04-04 16:50:07 +09:00
};
}
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Method ------------------------------ //
// --------------------------------------------------------------------- //
2016-04-04 16:50:07 +09:00
// common callback for both new and edit forms
methodCallback(error, document) {
this.setState({disabled: false});
if (error) { // error
console.log(error)
// add error to state
2016-04-04 16:50:07 +09:00
this.throwError({
content: error.message,
type: "error"
});
// run error callback if it exists
if (this.props.errorCallback) this.props.errorCallback(document, error);
} else { // success
2016-04-04 16:50:07 +09:00
this.clearErrors();
// reset form if this is a new document form
if (this.getFormType() === "new") this.refs.form.reset();
// run success callback if it exists
if (this.props.successCallback) this.props.successCallback(document);
// run close callback if it exists in context (i.e. we're inside a modal)
if (this.context.closeCallback) this.context.closeCallback();
}
}
2016-04-04 16:50:07 +09:00
// submit form handler
submitForm(data) {
this.setState({disabled: true});
2016-04-07 15:24:38 +09:00
const fields = this.getFieldNames();
const collection = this.props.collection;
// if there's a submit callback, run it
if (this.props.submitCallback) this.props.submitCallback();
if (this.getFormType() === "new") { // new document form
// remove any empty properties
let document = _.compactObject(Utils.flatten(data));
// add prefilled properties
if (this.props.prefilledProps) {
document = Object.assign(document, this.props.prefilledProps);
}
// call method with new document
Meteor.call(this.props.methodName, document, this.methodCallback);
} else { // edit document form
2016-04-07 22:21:01 +09:00
const document = this.getDocument();
// put all keys with data on $set
const set = _.compactObject(Utils.flatten(data));
// put all keys without data on $unset
const unsetKeys = _.difference(fields, _.keys(set));
const unset = _.object(unsetKeys, unsetKeys.map(()=>true));
// build modifier
const modifier = {$set: set};
if (!_.isEmpty(unset)) modifier.$unset = unset;
// call method with _id of document being edited and modifier
Meteor.call(this.props.methodName, document._id, modifier, this.methodCallback);
}
}
2016-04-07 15:29:02 +09:00
// --------------------------------------------------------------------- //
// ------------------------------- Render ------------------------------ //
// --------------------------------------------------------------------- //
render() {
2016-04-07 15:24:38 +09:00
// build fields array by iterating over the list of field names
let fields = this.getFieldNames().map(fieldName => {
// get schema for the current field
const fieldSchema = this.props.collection.simpleSchema()._schema[fieldName]
// add name, label, and type properties
let field = {
name: fieldName,
2016-04-08 10:29:32 +09:00
label: (typeof this.props.labelFunction === "function") ? this.props.labelFunction(fieldName) : fieldName,
2016-04-07 15:24:38 +09:00
type: fieldSchema.type,
2016-04-08 10:29:32 +09:00
control: fieldSchema.control,
layout: this.props.layout
2016-04-07 15:24:38 +09:00
}
2016-04-07 15:24:38 +09:00
// add value
2016-04-07 22:21:01 +09:00
field.value = this.getDocument() && Utils.deepValue(this.getDocument(), fieldName) ? Utils.deepValue(this.getDocument(), fieldName) : "";
2016-04-07 15:24:38 +09:00
// add options if they exist
if (fieldSchema.autoform && fieldSchema.autoform.options) {
field.options = typeof fieldSchema.autoform.options === "function" ? fieldSchema.autoform.options() : fieldSchema.autoform.options;
}
return field;
});
2016-04-07 22:21:01 +09:00
// console.log(fields)
2016-04-07 15:24:38 +09:00
// remove fields where control = "none"
fields = _.reject(fields, field => field.control === "none");
return (
2016-04-07 15:24:38 +09:00
<div className={"document-"+this.getFormType()}>
2016-04-08 10:29:32 +09:00
<Formsy.Form
onSubmit={this.submitForm}
disabled={this.state.disabled}
ref="form"
onChange={this.updateState}
>
{this.renderErrors()}
2016-04-07 15:24:38 +09:00
{fields.map(field => <FormComponent key={field.name} {...field} />)}
<Button type="submit" bsStyle="primary">Submit</Button>
2016-04-08 10:29:32 +09:00
{this.props.cancelCallback ? <a className="form-cancel" onClick={this.props.cancelCallback}>Cancel</a> : null}
</Formsy.Form>
</div>
)
}
}
NovaForm.propTypes = {
collection: React.PropTypes.object.isRequired,
document: React.PropTypes.object, // if a document is passed, this will be an edit form
currentUser: React.PropTypes.object,
2016-04-04 11:30:14 +09:00
submitCallback: React.PropTypes.func,
successCallback: React.PropTypes.func,
errorCallback: React.PropTypes.func,
methodName: React.PropTypes.string,
labelFunction: React.PropTypes.func,
2016-04-08 10:29:32 +09:00
prefilledProps: React.PropTypes.object,
layout: React.PropTypes.string,
cancelCallback: React.PropTypes.func
}
NovaForm.defaultPropTypes = {
layout: "horizontal"
}
NovaForm.contextTypes = {
closeCallback: React.PropTypes.func
}
2016-04-04 16:50:07 +09:00
NovaForm.childContextTypes = {
2016-04-07 12:43:41 +09:00
prefilledValues: React.PropTypes.object,
2016-04-07 15:24:38 +09:00
addToPrefilledValues: React.PropTypes.func,
throwError: React.PropTypes.func
2016-04-04 16:50:07 +09:00
}
module.exports = NovaForm;
export default NovaForm;