mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 01:51:40 -05:00
Work on form errors & validation
This commit is contained in:
parent
8d686ae9e1
commit
1d7cef5556
11 changed files with 383 additions and 135 deletions
|
@ -84,6 +84,7 @@ Datatable.propTypes = {
|
|||
showSearch: PropTypes.bool,
|
||||
newFormOptions: PropTypes.object,
|
||||
editFormOptions: PropTypes.object,
|
||||
emptyState: PropTypes.object,
|
||||
}
|
||||
|
||||
Datatable.defaultProps = {
|
||||
|
|
21
packages/vulcan-forms/lib/components/FieldErrors.jsx
Normal file
21
packages/vulcan-forms/lib/components/FieldErrors.jsx
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
|
||||
const FieldErrors = ({ errors }) => (
|
||||
<ul className="form-input-errors">
|
||||
{errors.map((error, index) => (
|
||||
<li key={index}>
|
||||
{error.message || (
|
||||
<FormattedMessage
|
||||
id={`errors.${error.data.type}`}
|
||||
values={{ ...error.data }}
|
||||
defaultMessage={JSON.stringify(error)}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
registerComponent('FieldErrors', FieldErrors);
|
|
@ -164,25 +164,49 @@ class Form extends Component {
|
|||
Get a field's value in a document // TODO: maybe not needed?
|
||||
|
||||
*/
|
||||
getValue = (fieldName, document) => {
|
||||
getValue = (fieldName, fieldSchema, document) => {
|
||||
if (typeof document[fieldName] !== 'undefined' && document[fieldName] !== null) {
|
||||
return document[fieldName];
|
||||
|
||||
let value = document[fieldName];
|
||||
// convert value type if needed
|
||||
if (fieldSchema.type.definitions[0].type === Number) value = Number(value);
|
||||
|
||||
// if value is an array of objects ({_id: '123'}, {_id: 'abc'}), flatten it into an array of strings (['123', 'abc'])
|
||||
// fallback to item itself if item._id is not defined (ex: item is not an object or item is just {slug: 'xxx'})
|
||||
// if (Array.isArray(field.value)) {
|
||||
// field.value = field.value.map(item => item._id || item);
|
||||
// }
|
||||
// TODO: not needed anymore?
|
||||
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Given an array field, get its nested schema
|
||||
|
||||
*/
|
||||
getNestedSchema = (fieldName) => {
|
||||
const arrayItemSchema = this.getSchema()[`${fieldName}.$`];
|
||||
return arrayItemSchema && arrayItemSchema.type.definitions[0].type._schema;
|
||||
}
|
||||
/*
|
||||
|
||||
Given a field's name, its schema, document, and parent, create the
|
||||
complete field object to be passed to the component
|
||||
|
||||
*/
|
||||
createField = (fieldName, fieldSchema, document, parentFieldName) => {
|
||||
// console.log('// createField', fieldName)
|
||||
createField = (fieldName, fieldSchema, document, parentFieldName, parentPath) => {
|
||||
|
||||
const fieldPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
|
||||
|
||||
// console.log('// createField', fieldPath)
|
||||
// console.log(fieldSchema)
|
||||
// console.log(document)
|
||||
// console.log('-> nested: ', fieldSchema.type.singleType === Array)
|
||||
|
||||
// console.log('-> nested: ', !!this.getNestedSchema(fieldName))
|
||||
|
||||
// store fieldSchema object in this.fieldSchemas
|
||||
this.fieldSchemas[fieldName] = fieldSchema;
|
||||
|
||||
|
@ -191,6 +215,7 @@ class Form extends Component {
|
|||
// intialize properties
|
||||
let field = {
|
||||
name: fieldName,
|
||||
path: fieldPath,
|
||||
datatype: fieldSchema.type,
|
||||
control: fieldSchema.control,
|
||||
layout: this.props.layout,
|
||||
|
@ -205,21 +230,10 @@ class Form extends Component {
|
|||
field.label = this.getLabel(fieldName);
|
||||
|
||||
// note: for nested fields, value will be null here and set by FormNested later
|
||||
const fieldValue = this.getValue(fieldName, document);
|
||||
|
||||
const fieldValue = this.getValue(fieldName, fieldSchema, document);
|
||||
// add value
|
||||
if (fieldValue) {
|
||||
field.value = fieldValue;
|
||||
|
||||
// convert value type if needed
|
||||
if (fieldSchema.type.definitions[0].type === Number) field.value = Number(field.value);
|
||||
|
||||
// if value is an array of objects ({_id: '123'}, {_id: 'abc'}), flatten it into an array of strings (['123', 'abc'])
|
||||
// fallback to item itself if item._id is not defined (ex: item is not an object or item is just {slug: 'xxx'})
|
||||
// if (Array.isArray(field.value)) {
|
||||
// field.value = field.value.map(item => item._id || item);
|
||||
// }
|
||||
// TODO: not needed anymore?
|
||||
}
|
||||
|
||||
// backward compatibility from 'autoform' to 'form'
|
||||
|
@ -301,25 +315,23 @@ class Form extends Component {
|
|||
// add document
|
||||
field.document = this.getDocument();
|
||||
|
||||
// add error state
|
||||
const validationError = _.findWhere(this.state.errors, { name: 'app.validation_error' });
|
||||
if (validationError) {
|
||||
const fieldErrors = _.filter(validationError.data.errors, error => error.data.fieldName === fieldName);
|
||||
if (fieldErrors) {
|
||||
field.errors = fieldErrors.map(error => ({ ...error, message: this.getErrorMessage(error) }));
|
||||
}
|
||||
}
|
||||
// add any relevant errors
|
||||
// const fieldErrors = _.filter(this.state.errors, error => error.data.name === fieldName);
|
||||
// if (fieldErrors) {
|
||||
// field.errors = fieldErrors.map(error => ({ ...error, message: this.getErrorMessage(error) }));
|
||||
// }
|
||||
|
||||
// nested fields: set control to "nested"
|
||||
if (fieldSchema.type.singleType === Array) {
|
||||
const nestedSchema = this.getNestedSchema(fieldName);
|
||||
|
||||
if (nestedSchema) {
|
||||
field.nestedSchema = nestedSchema;
|
||||
field.control = 'nested';
|
||||
// get nested schema
|
||||
field.nestedSchema = this.getSchema()[`${fieldName}.$`].type.definitions[0].type._schema; // TODO: do this better
|
||||
|
||||
// for each nested field, get field object by calling createField recursively
|
||||
field.nestedFields = this.getFieldNames(field.nestedSchema).map(subFieldName => {
|
||||
const subFieldSchema = field.nestedSchema[subFieldName];
|
||||
return this.createField(subFieldName, subFieldSchema, document, fieldName);
|
||||
return this.createField(subFieldName, subFieldSchema, document, fieldName, fieldPath);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -435,41 +447,7 @@ class Form extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
Render errors
|
||||
|
||||
*/
|
||||
renderErrors = () => {
|
||||
return (
|
||||
<div className="form-errors">
|
||||
{this.state.errors.map((error, index) => {
|
||||
let message;
|
||||
|
||||
if (error.data && error.data.errors) {
|
||||
// this error is a "multi-error" with multiple sub-errors
|
||||
|
||||
message = error.data.errors.map(error => {
|
||||
return {
|
||||
content: this.getErrorMessage(error),
|
||||
data: error.data,
|
||||
};
|
||||
});
|
||||
} else {
|
||||
// this is a regular error
|
||||
|
||||
message = {
|
||||
content:
|
||||
error.message ||
|
||||
this.context.intl.formatMessage({ id: error.id, defaultMessage: error.id }, error.data),
|
||||
};
|
||||
}
|
||||
|
||||
return <Components.FormFlash key={index} message={message} type="error" />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------- //
|
||||
// ------------------------------- Context ----------------------------- //
|
||||
|
@ -486,7 +464,7 @@ class Form extends Component {
|
|||
|
||||
// add error to state
|
||||
this.setState(prevState => ({
|
||||
errors: [...prevState.errors, graphQLError],
|
||||
errors: [...prevState.errors, ...graphQLError.data.errors],
|
||||
}));
|
||||
};
|
||||
|
||||
|
@ -560,6 +538,7 @@ class Form extends Component {
|
|||
addToSubmitForm: this.addToSubmitForm,
|
||||
addToSuccessForm: this.addToSuccessForm,
|
||||
addToFailureForm: this.addToFailureForm,
|
||||
errors: this.state.errors,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -584,7 +563,6 @@ class Form extends Component {
|
|||
this.addToDeletedValues(path);
|
||||
} else {
|
||||
set(prevState.currentValues, path, value);
|
||||
// dot.str(path, value, prevState.currentValues);
|
||||
}
|
||||
});
|
||||
return prevState;
|
||||
|
@ -783,7 +761,7 @@ class Form extends Component {
|
|||
return (
|
||||
<div className={'document-' + this.getFormType()}>
|
||||
<Formsy.Form onSubmit={this.submitForm} onKeyDown={this.formKeyDown} disabled={this.state.disabled} ref="form">
|
||||
{this.renderErrors()}
|
||||
<Components.FormErrors errors={this.state.errors}/>
|
||||
|
||||
{fieldGroups.map(group => (
|
||||
<Components.FormGroup key={group.name} {...group} updateCurrentValues={this.updateCurrentValues} />
|
||||
|
@ -870,6 +848,7 @@ Form.childContextTypes = {
|
|||
clearForm: PropTypes.func,
|
||||
getDocument: PropTypes.func,
|
||||
submitForm: PropTypes.func,
|
||||
errors: PropTypes.array,
|
||||
};
|
||||
|
||||
module.exports = Form;
|
||||
|
|
|
@ -52,7 +52,7 @@ class FormComponent extends PureComponent {
|
|||
renderComponent() {
|
||||
|
||||
// see https://facebook.github.io/react/warnings/unknown-prop.html
|
||||
const { control, group, updateCurrentValues, document, beforeComponent, afterComponent, limit, errors, nestedSchema, nestedFields, datatype, parentFieldName, itemIndex, ...rest } = this.props; // eslint-disable-line
|
||||
const { control, group, updateCurrentValues, document, beforeComponent, afterComponent, limit, errors, nestedSchema, nestedFields, datatype, parentFieldName, itemIndex, path, ...rest } = this.props; // eslint-disable-line
|
||||
|
||||
const properties = {
|
||||
value: '', // default value, will be overridden by `rest` if real value has been passed down through props
|
||||
|
@ -72,7 +72,7 @@ class FormComponent extends PureComponent {
|
|||
switch (this.props.control) {
|
||||
|
||||
case 'nested':
|
||||
return <Components.FormNested updateCurrentValues={updateCurrentValues} nestedSchema={nestedSchema} nestedFields={nestedFields} {...properties}/>;
|
||||
return <Components.FormNested path={path} updateCurrentValues={updateCurrentValues} nestedSchema={nestedSchema} nestedFields={nestedFields} datatype={datatype} {...properties}/>;
|
||||
|
||||
case 'number':
|
||||
return <Components.FormComponentNumber {...properties}/>;
|
||||
|
@ -145,12 +145,9 @@ class FormComponent extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
renderErrors = () => {
|
||||
return (
|
||||
<ul className='form-input-errors'>
|
||||
{this.props.errors.map((error, index) => <li key={index}>{error.message}</li>)}
|
||||
</ul>
|
||||
)
|
||||
getErrors = () => {
|
||||
const fieldErrors = this.context.errors.filter(error => error.data.name === this.props.path);
|
||||
return fieldErrors;
|
||||
}
|
||||
|
||||
showClear = () => {
|
||||
|
@ -175,14 +172,14 @@ class FormComponent extends PureComponent {
|
|||
|
||||
render() {
|
||||
|
||||
const hasErrors = this.props.errors && this.props.errors.length;
|
||||
const hasErrors = this.getErrors() && this.getErrors().length;
|
||||
const inputClass = classNames('form-input', `input-${this.props.name}`, `form-component-${this.props.control || 'default'}`,{'input-error': hasErrors});
|
||||
|
||||
return (
|
||||
<div className={inputClass}>
|
||||
{this.props.beforeComponent ? this.props.beforeComponent : null}
|
||||
{this.renderComponent()}
|
||||
{hasErrors ? this.renderErrors() : null}
|
||||
{hasErrors ? <Components.FieldErrors errors={this.getErrors()}/> : null}
|
||||
{this.showClear() ? this.renderClear() : null}
|
||||
{this.props.limit ? <div className={classNames('form-control-limit', {danger: this.state.limit < 10})}>{this.state.limit}</div> : null}
|
||||
{this.props.afterComponent ? this.props.afterComponent : null}
|
||||
|
@ -209,6 +206,7 @@ FormComponent.propTypes = {
|
|||
FormComponent.contextTypes = {
|
||||
intl: intlShape,
|
||||
addToDeletedValues: PropTypes.func,
|
||||
errors: PropTypes.array,
|
||||
};
|
||||
|
||||
registerComponent('FormComponent', FormComponent);
|
||||
|
|
64
packages/vulcan-forms/lib/components/FormErrors.jsx
Normal file
64
packages/vulcan-forms/lib/components/FormErrors.jsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { registerComponent } from 'meteor/vulcan:core';
|
||||
import { FormattedMessage } from 'meteor/vulcan:i18n';
|
||||
import Alert from 'react-bootstrap/lib/Alert';
|
||||
|
||||
const FormErrors = ({ errors }) => (
|
||||
<div className="form-errors">
|
||||
{!!errors.length && (
|
||||
<Alert className="flash-message" bsStyle="danger">
|
||||
<ul>
|
||||
{errors.map((error, index) => (
|
||||
<li key={index}>
|
||||
{error.message || (
|
||||
<FormattedMessage
|
||||
id={`errors.${error.data.type}`}
|
||||
values={{ ...error.data }}
|
||||
defaultMessage={JSON.stringify(error)}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
registerComponent('FormErrors', FormErrors);
|
||||
|
||||
// /*
|
||||
|
||||
// Render errors
|
||||
|
||||
// */
|
||||
// renderErrors = () => {
|
||||
// return (
|
||||
// <div className="form-errors">
|
||||
// {this.state.errors.map((error, index) => {
|
||||
// let message;
|
||||
|
||||
// if (error.data && error.data.errors) {
|
||||
// // this error is a "multi-error" with multiple sub-errors
|
||||
|
||||
// message = error.data.errors.map(error => {
|
||||
// return {
|
||||
// content: this.getErrorMessage(error),
|
||||
// data: error.data,
|
||||
// };
|
||||
// });
|
||||
// } else {
|
||||
// // this is a regular error
|
||||
|
||||
// message = {
|
||||
// content:
|
||||
// error.message ||
|
||||
// this.context.intl.formatMessage({ id: error.id, defaultMessage: error.id }, error.data),
|
||||
// };
|
||||
// }
|
||||
|
||||
// return <Components.FormFlash key={index} message={message} type="error" />;
|
||||
// })}
|
||||
// </div>
|
||||
// );
|
||||
// };
|
|
@ -3,14 +3,27 @@ import PropTypes from 'prop-types';
|
|||
import { Components, registerComponent } from 'meteor/vulcan:core';
|
||||
import Button from 'react-bootstrap/lib/Button';
|
||||
|
||||
const FormNestedItem = ({ isDeleted, nestedFields, name, subDocument, removeItem, ...props }) => {
|
||||
const FormNestedItem = (
|
||||
{ isDeleted, nestedFields, name, path, subDocument, removeItem, itemIndex, ...props },
|
||||
{ errors }
|
||||
) => {
|
||||
return (
|
||||
<div className={`form-nested-item ${isDeleted ? 'form-nested-item-deleted' : ''}`}>
|
||||
<div className="form-nested-item-inner">
|
||||
{nestedFields.map((field, i) => {
|
||||
// note: default value to '' to avoid uncontrolled component error
|
||||
const value = subDocument && subDocument[field.name] || '';
|
||||
return <Components.FormComponent key={i} {...props} {...field} value={value} />;
|
||||
let value = (subDocument && subDocument[field.name]) || '';
|
||||
if (props.control === 'number') value = Number(value);
|
||||
return (
|
||||
<Components.FormComponent
|
||||
key={i}
|
||||
{...props}
|
||||
{...field}
|
||||
path={`${path}.${field.name}`}
|
||||
value={value}
|
||||
itemIndex={itemIndex}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="form-nested-item-remove">
|
||||
|
@ -23,21 +36,24 @@ const FormNestedItem = ({ isDeleted, nestedFields, name, subDocument, removeItem
|
|||
✖️
|
||||
</Button>
|
||||
</div>
|
||||
<div className="form-nested-item-deleted-overlay"/>
|
||||
<div className="form-nested-item-deleted-overlay" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FormNestedItem.contextTypes = {
|
||||
errors: PropTypes.array,
|
||||
};
|
||||
|
||||
registerComponent('FormNestedItem', FormNestedItem);
|
||||
|
||||
class FormNested extends PureComponent {
|
||||
|
||||
addItem = () => {
|
||||
this.props.updateCurrentValues({[`${this.props.name}.${this.props.value.length}`] : {}});
|
||||
this.props.updateCurrentValues({ [`${this.props.path}.${this.props.value.length}`]: {} });
|
||||
};
|
||||
|
||||
removeItem = index => {
|
||||
this.props.updateCurrentValues({[`${this.props.name}.${index}`] : null});
|
||||
this.props.updateCurrentValues({ [`${this.props.path}.${index}`]: null });
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -47,7 +63,7 @@ class FormNested extends PureComponent {
|
|||
look for the presence of 'addresses.1')
|
||||
*/
|
||||
isDeleted = index => {
|
||||
return this.context.deletedValues.includes(`${this.props.name}.${index}`);
|
||||
return this.context.deletedValues.includes(`${this.props.path}.${index}`);
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -56,13 +72,22 @@ class FormNested extends PureComponent {
|
|||
<label className="control-label col-sm-3">{this.props.label}</label>
|
||||
<div className="col-sm-9">
|
||||
{this.props.value &&
|
||||
this.props.value.map((subDocument, i) => (
|
||||
!this.isDeleted(i) && <FormNestedItem key={i} itemIndex={i} {...this.props} subDocument={subDocument} removeItem={() => {this.removeItem(i)}} />
|
||||
))}
|
||||
<Button
|
||||
bsStyle="success"
|
||||
onClick={this.addItem}
|
||||
>
|
||||
this.props.value.map(
|
||||
(subDocument, i) =>
|
||||
!this.isDeleted(i) && (
|
||||
<FormNestedItem
|
||||
{...this.props}
|
||||
key={i}
|
||||
itemIndex={i}
|
||||
subDocument={subDocument}
|
||||
path={`${this.props.path}.${i}`}
|
||||
removeItem={() => {
|
||||
this.removeItem(i);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<Button bsStyle="success" onClick={this.addItem}>
|
||||
➕
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -72,7 +97,7 @@ class FormNested extends PureComponent {
|
|||
}
|
||||
|
||||
FormNested.contextTypes = {
|
||||
deletedValues: PropTypes.array
|
||||
}
|
||||
deletedValues: PropTypes.array,
|
||||
};
|
||||
|
||||
registerComponent('FormNested', FormNested);
|
||||
|
|
|
@ -12,6 +12,8 @@ import '../components/bootstrap/Url.jsx';
|
|||
import '../components/bootstrap/Date.jsx';
|
||||
|
||||
import '../components/Flash.jsx';
|
||||
import '../components/FieldErrors.jsx';
|
||||
import '../components/FormErrors.jsx';
|
||||
import '../components/FormComponent.jsx';
|
||||
import '../components/FormNested.jsx';
|
||||
import '../components/FormGroup.jsx';
|
||||
|
|
|
@ -134,4 +134,7 @@ addStrings('en', {
|
|||
|
||||
"admin": "Admin",
|
||||
"notifications": "Notifications",
|
||||
|
||||
"errors.expectedType": `Expected a field of type {dataType}, got “{value}” instead.`,
|
||||
"errors.required": `Field “{name}” is required.`,
|
||||
});
|
||||
|
|
|
@ -6,10 +6,10 @@ String.prototype.replaceAll = function(search, replacement) {
|
|||
return target.replace(new RegExp(search, 'g'), replacement);
|
||||
};
|
||||
|
||||
const FormattedMessage = ({ id, values, defaultMessage, html = false }) => {
|
||||
const FormattedMessage = ({ id, values, defaultMessage = '', html = false }) => {
|
||||
const messages = Strings[getSetting('locale', 'en')] || {};
|
||||
let message = messages[id] || defaultMessage;
|
||||
if (values) {
|
||||
if (message && values) {
|
||||
_.forEach(values, (value, key) => {
|
||||
message = message.replaceAll(`{${key}}`, value);
|
||||
});
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
*/
|
||||
export const validateDocument = (document, collection, context) => {
|
||||
|
||||
const { Users, currentUser } = context;
|
||||
const schema = collection.simpleSchema()._schema;
|
||||
|
||||
|
@ -18,44 +17,40 @@ export const validateDocument = (document, collection, context) => {
|
|||
|
||||
// Check validity of inserted document
|
||||
_.forEach(document, (value, fieldName) => {
|
||||
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
// 1. check that the current user has permission to insert each field
|
||||
if (!fieldSchema || !Users.canInsertField (currentUser, fieldSchema)) {
|
||||
if (!fieldSchema || !Users.canInsertField(currentUser, fieldSchema)) {
|
||||
validationErrors.push({
|
||||
id: 'app.disallowed_property_detected',
|
||||
fieldName
|
||||
id: 'app.disallowed_property_detected',
|
||||
fieldName,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. check field lengths
|
||||
if (fieldSchema.limit && value.length > fieldSchema.limit) {
|
||||
validationErrors.push({
|
||||
id: 'app.field_is_too_long',
|
||||
data: {fieldName, limit: fieldSchema.limit}
|
||||
id: 'app.field_is_too_long',
|
||||
data: { fieldName, limit: fieldSchema.limit },
|
||||
});
|
||||
}
|
||||
|
||||
// 3. check that fields have the proper type
|
||||
// TODO
|
||||
|
||||
});
|
||||
|
||||
// 4. check that required fields have a value
|
||||
_.keys(schema).forEach(fieldName => {
|
||||
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
if ((fieldSchema.required || !fieldSchema.optional) && typeof document[fieldName] === 'undefined') {
|
||||
validationErrors.push({
|
||||
id: 'app.required_field_missing',
|
||||
data: {fieldName}
|
||||
id: 'app.required_field_missing',
|
||||
data: { fieldName },
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
// 5. still run SS validation for now for backwards compatibility
|
||||
try {
|
||||
collection.simpleSchema().validate(document);
|
||||
|
@ -64,13 +59,12 @@ export const validateDocument = (document, collection, context) => {
|
|||
console.log(error);
|
||||
validationErrors.push({
|
||||
id: 'app.schema_validation_error',
|
||||
data: {message: error.message}
|
||||
data: { message: error.message },
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
|
@ -84,7 +78,6 @@ export const validateDocument = (document, collection, context) => {
|
|||
|
||||
*/
|
||||
export const validateModifier = (modifier, document, collection, context) => {
|
||||
|
||||
const { Users, currentUser } = context;
|
||||
const schema = collection.simpleSchema()._schema;
|
||||
const set = modifier.$set;
|
||||
|
@ -94,61 +87,223 @@ export const validateModifier = (modifier, document, collection, context) => {
|
|||
|
||||
// 1. check that the current user has permission to edit each field
|
||||
const modifiedProperties = _.keys(set).concat(_.keys(unset));
|
||||
modifiedProperties.forEach(function (fieldName) {
|
||||
modifiedProperties.forEach(function(fieldName) {
|
||||
var field = schema[fieldName];
|
||||
if (!field || !Users.canEditField(currentUser, field, document)) {
|
||||
validationErrors.push({
|
||||
id: 'app.disallowed_property_detected',
|
||||
fieldName
|
||||
id: 'app.disallowed_property_detected',
|
||||
data: {name: fieldName},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check validity of set modifier
|
||||
_.forEach(set, (value, fieldName) => {
|
||||
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
// 2. check field lengths
|
||||
if (fieldSchema.limit && value.length > fieldSchema.limit) {
|
||||
validationErrors.push({
|
||||
id: 'app.field_is_too_long',
|
||||
data: {fieldName, limit: fieldSchema.limit}
|
||||
id: 'app.field_is_too_long',
|
||||
data: { name: fieldName, limit: fieldSchema.limit },
|
||||
});
|
||||
}
|
||||
|
||||
// 3. check that fields have the proper type
|
||||
// TODO
|
||||
});
|
||||
|
||||
// // 4. check that required fields have a value
|
||||
// // when editing, we only want to require fields that are actually part of the form
|
||||
// // so we make sure required keys are present in the $unset object
|
||||
// _.keys(schema).forEach(fieldName => {
|
||||
// const fieldSchema = schema[fieldName];
|
||||
|
||||
// if (unset[fieldName] && (fieldSchema.required || !fieldSchema.optional) && typeof set[fieldName] === 'undefined') {
|
||||
// validationErrors.push({
|
||||
// id: 'app.required_field_missing',
|
||||
// data: { name: fieldName },
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// 5. still run SS validation for now for backwards compatibility
|
||||
const validationContext = collection.simpleSchema().newContext();
|
||||
validationContext.validate({ $set: set, $unset: unset }, { modifier: true });
|
||||
|
||||
if (!validationContext.isValid()) {
|
||||
const errors = validationContext.validationErrors();
|
||||
console.log('// validationContext');
|
||||
console.log(validationContext.isValid());
|
||||
console.log(errors);
|
||||
errors.forEach(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log(error);
|
||||
validationErrors.push({
|
||||
id: 'app.schema_validation_error',
|
||||
data: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
|
||||
The following versions were written to be more SimpleSchema-agnostic, but
|
||||
are not currently used
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
If document is not trusted, run validation steps:
|
||||
|
||||
1. Check that the current user has permission to edit each field
|
||||
2. Check field lengths
|
||||
3. Check field types
|
||||
4. Check for missing fields
|
||||
5. Run SimpleSchema validation step (for now)
|
||||
|
||||
*/
|
||||
export const validateDocumentNotUsed = (document, collection, context) => {
|
||||
const { Users, currentUser } = context;
|
||||
const schema = collection.simpleSchema()._schema;
|
||||
|
||||
let validationErrors = [];
|
||||
|
||||
// Check validity of inserted document
|
||||
_.forEach(document, (value, fieldName) => {
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
// 1. check that the current user has permission to insert each field
|
||||
if (!fieldSchema || !Users.canInsertField(currentUser, fieldSchema)) {
|
||||
validationErrors.push({
|
||||
id: 'app.disallowed_property_detected',
|
||||
fieldName,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. check field lengths
|
||||
if (fieldSchema.limit && value.length > fieldSchema.limit) {
|
||||
validationErrors.push({
|
||||
id: 'app.field_is_too_long',
|
||||
data: { fieldName, limit: fieldSchema.limit },
|
||||
});
|
||||
}
|
||||
|
||||
// 3. check that fields have the proper type
|
||||
// TODO
|
||||
});
|
||||
|
||||
// 4. check that required fields have a value
|
||||
_.keys(schema).forEach(fieldName => {
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
if ((fieldSchema.required || !fieldSchema.optional) && typeof document[fieldName] === 'undefined') {
|
||||
validationErrors.push({
|
||||
id: 'app.required_field_missing',
|
||||
data: { fieldName },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 5. still run SS validation for now for backwards compatibility
|
||||
try {
|
||||
collection.simpleSchema().validate(document);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
validationErrors.push({
|
||||
id: 'app.schema_validation_error',
|
||||
data: { message: error.message },
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
If document is not trusted, run validation steps:
|
||||
|
||||
1. Check that the current user has permission to insert each field
|
||||
2. Check field lengths
|
||||
3. Check field types
|
||||
4. Check for missing fields
|
||||
5. Run SimpleSchema validation step (for now)
|
||||
|
||||
*/
|
||||
export const validateModifierNotUsed = (modifier, document, collection, context) => {
|
||||
const { Users, currentUser } = context;
|
||||
const schema = collection.simpleSchema()._schema;
|
||||
const set = modifier.$set;
|
||||
const unset = modifier.$unset;
|
||||
|
||||
let validationErrors = [];
|
||||
|
||||
// 1. check that the current user has permission to edit each field
|
||||
const modifiedProperties = _.keys(set).concat(_.keys(unset));
|
||||
modifiedProperties.forEach(function(fieldName) {
|
||||
var field = schema[fieldName];
|
||||
if (!field || !Users.canEditField(currentUser, field, document)) {
|
||||
validationErrors.push({
|
||||
id: 'app.disallowed_property_detected',
|
||||
data: {name: fieldName},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check validity of set modifier
|
||||
_.forEach(set, (value, fieldName) => {
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
// 2. check field lengths
|
||||
if (fieldSchema.limit && value.length > fieldSchema.limit) {
|
||||
validationErrors.push({
|
||||
id: 'app.field_is_too_long',
|
||||
data: { name: fieldName, limit: fieldSchema.limit },
|
||||
});
|
||||
}
|
||||
|
||||
// 3. check that fields have the proper type
|
||||
// TODO
|
||||
});
|
||||
|
||||
// 4. check that required fields have a value
|
||||
// when editing, we only want to require fields that are actually part of the form
|
||||
// so we make sure required keys are present in the $unset object
|
||||
_.keys(schema).forEach(fieldName => {
|
||||
|
||||
const fieldSchema = schema[fieldName];
|
||||
|
||||
if (unset[fieldName] && (fieldSchema.required || !fieldSchema.optional) && typeof set[fieldName] === 'undefined') {
|
||||
validationErrors.push({
|
||||
id: 'app.required_field_missing',
|
||||
data: {fieldName}
|
||||
id: 'app.required_field_missing',
|
||||
data: { name: fieldName },
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// 5. still run SS validation for now for backwards compatibility
|
||||
try {
|
||||
collection.simpleSchema().validate({$set: set, $unset: unset}, { modifier: true });
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
validationErrors.push({
|
||||
id: 'app.schema_validation_error',
|
||||
data: {message: error.message}
|
||||
const validationContext = collection.simpleSchema().newContext();
|
||||
validationContext.validate({ $set: set, $unset: unset }, { modifier: true });
|
||||
|
||||
if (!validationContext.isValid()) {
|
||||
const errors = validationContext.validationErrors();
|
||||
console.log('// validationContext');
|
||||
console.log(validationContext.isValid());
|
||||
console.log(errors);
|
||||
errors.forEach(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
// console.log(error);
|
||||
validationErrors.push({
|
||||
id: 'app.schema_validation_error',
|
||||
data: error,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return validationErrors;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -130,9 +130,9 @@ export const editMutation = async ({ collection, documentId, set = {}, unset = {
|
|||
debug('// editMutation');
|
||||
debug('// collectionName: ', collection._name);
|
||||
debug('// documentId: ', documentId);
|
||||
// debug('// set: ', set);
|
||||
// debug('// unset: ', unset);
|
||||
// debug('// document: ', document);
|
||||
debug('// set: ', set);
|
||||
debug('// unset: ', unset);
|
||||
debug('// document: ', document);
|
||||
|
||||
if (validate) {
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue