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

238 lines
7.2 KiB
JavaScript
Raw Normal View History

/*
* Schema converter/getters
*/
import Users from 'meteor/vulcan:users';
import _filter from 'lodash/filter';
import _keys from 'lodash/keys';
/* 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) => {
2018-03-26 14:49:05 +09:00
if (schema._schema) {
let jsonSchema = {};
2018-03-26 14:49:05 +09:00
Object.keys(schema._schema).forEach(fieldName => {
// exclude array fields
if (fieldName.includes('$')) {
return;
}
2018-03-26 14:49:05 +09:00
// 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);
2018-03-26 14:49:05 +09:00
} else {
// subSchema is a full schema with multiple fields (eg array of objects)
if (flatten) {
jsonSchema = { ...jsonSchema, ...convertedSubSchema };
} else {
jsonSchema[fieldName].schema = convertedSubSchema;
}
2018-03-26 14:49:05 +09:00
}
}
});
return jsonSchema;
} else {
return null;
}
};
/*
Get a JSON object representing a field's schema
*/
export const getFieldSchema = (fieldName, schema) => {
let fieldSchema = {};
schemaProperties.forEach(property => {
2018-03-26 14:49:05 +09:00
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',
2018-03-26 14:27:45 +09:00
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'fieldProperties',
'intl'
2018-03-26 14:27:45 +09:00
];
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)
2018-03-26 14:27:45 +09:00
'order', // position in the form
2018-03-26 14:49:05 +09:00
'group', // form fieldset group
2018-03-26 14:27:45 +09:00
'description',
'beforeComponent',
'afterComponent',
'placeholder',
'options',
'query',
'fieldProperties'
2018-03-26 14:49:05 +09:00
];