VulcanJS / Vulcan

🌋 A toolkit to quickly build apps with React, GraphQL & Meteor
http://vulcanjs.org
MIT License
7.98k stars 1.89k forks source link

New Feature: Field Type Decorators #2533

Open SachaG opened 4 years ago

SachaG commented 4 years ago

Let's imagine a complex form component such as a map that lets you select a location stored as a { lat, lng } pair. Currently we can do input: Map which activates the correct form input component.

But the component won't be functional unless we also specify the correct type (a SimpleSchema object with lat and lng subfields), GraphQL type (including input types), and maybe even validation (lat and lng have to be floats contained between x and y).

Currently all this has to be done manually, but we could automate it via a pattern I'm calling "field type templates" until I find a better name (field factories? field models?):

{
  location: makeMap('location', fieldSchema, options)
}

The makeMap template function would take a field name, field schema, and some options, and return a new schema that includes all the elements mentioned above. That field schema is then included in the collection schema as before.

Here's a real-world example for a future Likert Scale component:

import { addGraphQLSchema } from 'meteor/vulcan:core';
import SimpleSchema from 'simpl-schema';

export const makeLikert = (fieldName, field) => {
  // get typeName from fieldName unless it's already specified in field object
  const { typeName = `${fieldName}Type` } = field;

  if (!field.options) {
    throw new Error(`Likert fields need an 'options' property`);
  }

  // build SimpleSchema type object for validation
  const typeObject = {};
  field.options.forEach(({ value }) => {
    typeObject[value] = {
      type: SimpleSchema.Integer,
    };
  });

  // create GraphQL types for main type, create input type, and update input type
  addGraphQLSchema(`type ${typeName} {
  ${field.options.map(({ value }) => `${value}: Int`).join('\n  ')}
}

input Create${typeName}DataInput {
  ${field.options.map(({ value }) => `${value}: Int`).join('\n  ')}
}

input Update${typeName}DataInput {
  ${field.options.map(({ value }) => `${value}: Int`).join('\n  ')}
}
  `);

  // add additional field object properties
  const likertField = {
    ...field,
    type: new SimpleSchema(typeObject),
    input: 'likert',
    typeName,
  };

  return likertField;
};
SachaG commented 4 years ago

Maybe "decorator" would be a better name than "template"?