Vulcan/packages/vulcan-forms/lib/modules/schema_utils.js

236 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Schema converter/getters
*/
import Users from 'meteor/vulcan:users';
import _ from 'lodash';
/* getters */
// filter out fields with "." or "$"
export const getValidFields = schema => {
return Object.keys(schema).filter(fieldName => !fieldName.includes('$') && !fieldName.includes('.'));
};
export const getReadableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canRead || schema[fieldName].viewableBy);
};
export const getCreateableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canCreate || schema[fieldName].insertableBy);
};
export const getUpdateableFields = schema => {
// OpenCRUD backwards compatibility
return getValidFields(schema).filter(fieldName => schema[fieldName].canUpdate || schema[fieldName].editableBy);
};
/* permissions */
/**
* @method Mongo.Collection.getInsertableFields
* Get an array of all fields editable by a specific user for a given collection
* @param {Object} user the user for which to check field permissions
*/
export const getInsertableFields = function(schema, user) {
const fields = _.filter(_.keys(schema), function(fieldName) {
var field = schema[fieldName];
return Users.canCreateField(user, field);
});
return fields;
};
/**
* @method Mongo.Collection.getEditableFields
* Get an array of all fields editable by a specific user for a given collection (and optionally document)
* @param {Object} user the user for which to check field permissions
*/
export const getEditableFields = function(schema, user, document) {
const fields = _.filter(_.keys(schema), function(fieldName) {
var field = schema[fieldName];
return Users.canUpdateField(user, field, document);
});
return fields;
};
/*
Convert a nested SimpleSchema schema into a JSON object
If flatten = true, will create a flat object instead of nested tree
*/
export const convertSchema = (schema, flatten = false) => {
if (schema._schema) {
let jsonSchema = {};
Object.keys(schema._schema).forEach(fieldName => {
// exclude array fields
if (fieldName.includes('$')) {
return;
}
// extract schema
jsonSchema[fieldName] = getFieldSchema(fieldName, schema);
// check for existence of nested field
// and get its schema if possible or its type otherwise
const subSchemaOrType = getNestedFieldSchemaOrType(fieldName, schema);
if (subSchemaOrType) {
// if nested field exists, call convertSchema recursively
const convertedSubSchema = convertSchema(subSchemaOrType);
// nested schema can be a field schema ({type, canRead, etc.}) (convertedSchema will be null)
// or a schema on its own with subfields (convertedSchema will return smth)
if (!convertedSubSchema) {
// subSchema is a simple field in this case (eg array of numbers)
jsonSchema[fieldName].field = getFieldSchema(`${fieldName}.$`, schema);
} else {
// subSchema is a full schema with multiple fields (eg array of objects)
if (flatten) {
jsonSchema = { ...jsonSchema, ...convertedSubSchema };
} else {
jsonSchema[fieldName].schema = convertedSubSchema;
}
}
}
});
return jsonSchema;
} else {
return null;
}
};
/*
Get a JSON object representing a field's schema
*/
export const getFieldSchema = (fieldName, schema) => {
let fieldSchema = {};
schemaProperties.forEach(property => {
const propertyValue = schema.get(fieldName, property);
if (propertyValue) {
fieldSchema[property] = propertyValue;
}
});
return fieldSchema;
};
// type is an array due to the possibility of using SimpleSchema.oneOf
// right now we support only fields with one type
export const getSchemaType = schema => schema.type.definitions[0].type;
const getArrayNestedSchema = (fieldName, schema) => {
const arrayItemSchema = schema._schema[`${fieldName}.$`];
const nestedSchema = arrayItemSchema && getSchemaType(arrayItemSchema);
return nestedSchema;
};
// nested object fields type is of the form "type: new SimpleSchema({...})"
// so they should possess a "_schema" prop
const isNestedSchemaField = fieldSchema => {
const fieldType = getSchemaType(fieldSchema);
//console.log('fieldType', typeof fieldType, fieldType._schema)
return fieldType && !!fieldType._schema;
};
const getObjectNestedSchema = (fieldName, schema) => {
const fieldSchema = schema._schema[fieldName];
if (!isNestedSchemaField(fieldSchema)) return null;
const nestedSchema = fieldSchema && getSchemaType(fieldSchema);
return nestedSchema;
};
/*
Given an array field, get its nested schema
If the field is not an object, this will return the subfield type instead
*/
export const getNestedFieldSchemaOrType = (fieldName, schema) => {
const arrayItemSchema = getArrayNestedSchema(fieldName, schema);
if (!arrayItemSchema) {
// look for an object schema
const objectItemSchema = getObjectNestedSchema(fieldName, schema);
// no schema was found
if (!objectItemSchema) return null;
return objectItemSchema;
}
return arrayItemSchema;
};
export const schemaProperties = [
'type',
'label',
'optional',
'required',
'min',
'max',
'exclusiveMin',
'exclusiveMax',
'minCount',
'maxCount',
'allowedValues',
'regEx',
'blackbox',
'trim',
'custom',
'defaultValue',
'autoValue',
'hidden', // hidden: true means the field is never shown in a form no matter what
'mustComplete', // mustComplete: true means the field is required to have a complete profile
'profile', // profile: true means the field is shown on user profiles
'form', // form placeholder
'inputProperties', // form placeholder
'control', // SmartForm control (String or React component)
'input', // SmartForm control (String or React component)
'autoform', // legacy form placeholder; backward compatibility (not used anymore)
'order', // position in the form
'group', // form fieldset group
'onInsert', // field insert callback
'onEdit', // field edit callback
'onRemove', // field remove callback
'canRead',
'canCreate',
'canUpdate',
'viewableBy', // OpenCRUD backwards compatibility
'insertableBy', // OpenCRUD backwards compatibility
'editableBy', // OpenCRUD backwards compatibility
'resolveAs',
'searchable',
'description',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'fieldProperties',
'intl'
];
export const formProperties = [
'optional',
'required',
'min',
'max',
'exclusiveMin',
'exclusiveMax',
'minCount',
'maxCount',
'allowedValues',
'regEx',
'blackbox',
'trim',
'custom',
'defaultValue',
'autoValue',
'mustComplete', // mustComplete: true means the field is required to have a complete profile
'form', // form placeholder
'inputProperties', // form placeholder
'control', // SmartForm control (String or React component)
'input', // SmartForm control (String or React component)
'order', // position in the form
'group', // form fieldset group
'description',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'fieldProperties'
];