Enable required locale validation for individual locales; add support for intl: true on schema fields

This commit is contained in:
SachaG 2018-06-28 21:35:44 +02:00
parent a01ca2ab6e
commit 2d2d1033b5
6 changed files with 86 additions and 31 deletions

View file

@ -196,10 +196,12 @@ class FormComponent extends Component {
Get errors from Form state through context
Note: we use `includes` to get all errors from nested components, which have longer paths
*/
getErrors = errors => {
errors = errors || this.props.errors;
const fieldErrors = errors.filter(error => error.path === this.props.path);
const fieldErrors = errors.filter(error => error.path.includes(this.props.path));
return fieldErrors;
};

View file

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Components, registerComponent, Locales } from 'meteor/vulcan:core';
import omit from 'lodash/omit';
class FormIntl extends PureComponent {
@ -11,20 +12,20 @@ class FormIntl extends PureComponent {
so we just use the order of the Locales array.
*/
getLocalePath = (locale, defaultIndex) => {
getLocalePath = (defaultIndex) => {
return `${this.props.path}_intl.${defaultIndex}`;
}
render() {
// do not pass FormIntl's own value, inputProperties, and intlInput props down
const properties = _.omit(this.props, 'value', 'inputProperties', 'intlInput');
const properties = omit(this.props, 'value', 'inputProperties', 'intlInput');
return (
<div className="form-intl">
{Locales.map((locale, i) => (
<div className={`form-intl-${locale.id}`} key={locale.id}>
<Components.FormComponent {...properties} label={`${this.props.label} (${locale.id})`} path={this.getLocalePath(locale.id, i)} locale={locale.id} />
<Components.FormComponent {...properties} label={`${this.props.label} (${locale.id})`} path={this.getLocalePath(i)} locale={locale.id} />
</div>
))}
</div>

View file

@ -1,12 +1,12 @@
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import { addGraphQLCollection, addGraphQLQuery, addGraphQLMutation, addGraphQLResolvers, addToGraphQLContext } from './graphql.js';
import { addGraphQLCollection, addToGraphQLContext } from './graphql.js';
import { Utils } from './utils.js';
import { runCallbacks } from './callbacks.js';
import { getSetting, registerSetting } from './settings.js';
import { registerFragment, getDefaultFragmentText } from './fragments.js';
import escapeStringRegexp from 'escape-string-regexp';
import { multiQueryTemplate, singleQueryTemplate, createMutationTemplate, updateMutationTemplate, upsertMutationTemplate, deleteMutationTemplate } from './graphql_templates';
import { Locales, getIntlString } from './intl';
const wrapAsync = (Meteor.wrapAsync)? Meteor.wrapAsync : Meteor._wrapAsync;
// import { debug } from './debug.js';
@ -119,6 +119,33 @@ Mongo.Collection.prototype.helpers = function(helpers) {
});
};
/*
Custom validation function to check for required locales
See https://github.com/aldeed/simple-schema-js#custom-field-validation
*/
const validateIntlField = function () {
let errors = [];
// if field is required, go through locales to check which one are required
if (!this.definition.optional) {
const requiredLocales = Locales.filter(locale => locale.required);
requiredLocales.forEach((locale, index) => {
const strings = this.value;
const hasString = strings.some(s => s.locale === locale.id && s.value);
if (!hasString) {
errors.push({ path: this.key, locale: locale.id, index, name: `${this.key.replace('_intl', '')} (${locale.id})` });
}
});
}
// hack to work around the fact that custom validation function can only return a single string
return `intlError|${JSON.stringify(errors)}`;
}
export const createCollection = options => {
const { typeName, collectionName = getCollectionName(typeName), schema, generateGraphQLSchema = true, dbCollectionName } = options;
@ -138,23 +165,23 @@ export const createCollection = options => {
// generate foo_intl fields
Object.keys(schema).forEach(fieldName => {
const fieldSchema = schema[fieldName];
if (fieldSchema.type && fieldSchema.type.name === 'IntlString') {
if (fieldSchema.intl || (fieldSchema.type && fieldSchema.type.name === 'IntlString')) {
// we have at least one intl field
hasIntlFields = true;
// make non-intl field optional
schema[fieldName].optional = true;
schema[`${fieldName}_intl`] = {
...schema[fieldName], // copy properties from regular field
hidden: true,
type: Array,
custom: validateIntlField,
}
schema[`${fieldName}_intl.$`] = {
type: Object,
blackbox: true,
type: getIntlString(),
}
// make non-intl field optional
schema[fieldName].optional = true;
}
});

View file

@ -51,6 +51,8 @@ SimpleSchema.extendOptions([
'query', // field-specific data loading query
'selectable', // field can be used as part of a selector when querying for data
'orderable', // field can be used to order results when querying for data
'intl', // set to `true` to make a field international
]);
// eslint-disable-next-line no-undef

View file

@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
import { Strings } from './strings';
export const Locales = [];
@ -31,14 +30,16 @@ Generate custom IntlString SimpleSchema type
*/
export const getIntlString = () => {
const schema = {};
Object.keys(Strings).forEach(locale => {
schema[locale] = {
const schema = {
locale: {
type: String,
optional: true,
};
});
optional: false,
},
value: {
type: String,
optional: false,
}
};
const IntlString = new SimpleSchema(schema);
IntlString.name = 'IntlString';

View file

@ -47,11 +47,22 @@ export const validateDocument = (document, collection, context) => {
errors.forEach(error => {
// eslint-disable-next-line no-console
// console.log(error);
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: error,
});
if (error.type.includes('intlError')) {
const intlErrors = JSON.parse(error.type.replace('intlError|', ''));
intlErrors.forEach(intlError => {
validationErrors.push({
id: `errors.required`,
path: `${intlError.path}.${intlError.index}`,
properties: intlError,
});
});
} else {
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: error,
});
}
});
}
@ -96,11 +107,22 @@ export const validateModifier = (modifier, document, collection, context) => {
errors.forEach(error => {
// eslint-disable-next-line no-console
// console.log(error);
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: error,
});
if (error.type.includes('intlError')) {
const intlErrors = JSON.parse(error.type.replace('intlError|', ''));
intlErrors.forEach(intlError => {
validationErrors.push({
id: `errors.required`,
path: `${intlError.path}.${intlError.index}`,
properties: intlError,
});
});
} else {
validationErrors.push({
id: `errors.${error.type}`,
path: error.name,
properties: error,
});
}
});
}