Uniforms: Generate field value automatically with SimpleSchema@2

Created on 13 Jul 2017  路  7Comments  路  Source: vazco/uniforms


I am trying to generate a slug based on a "title" field, but I have problems understanding the mechanisms required to make this work.
I'm using Meteor (with ValidatedMethod), SimpleSchema (npm version) and Uniforms for Semantic UI.

My Schema:

Schemas.MySchema = new SimpleSchema({
    title: {
        type: String,
        label: "Title",
        max: 200
    },
    slug: {
        type: String,
        autoValue: function() {
            let title = this.field('title').value;
            return title.toString().toLowerCase()
                .replace(/\s+/g, '-')           // Replace spaces with -
                .replace(/[^\w\-]+/g, '')       // Remove all non-word chars
                .replace(/\-\-+/g, '-')         // Replace multiple - with single -
                .replace(/^-+/, '')             // Trim - from start of text
                .replace(/-+$/, '');            // Trim - from end of text
        },
    }
}

I use <AutoForm> for inserting and a <ModifierForm> for updating (forms are actually more complex, here I show only the two fields):

// ADDING A NEW DOCUMENT
<AutoForm 
    ref={ref => formRef = ref}
    schema={Schemas.MySchema} 
    onSubmit={doc => this.handleSubmit(doc, formRef)}
>
    <TextField name="title" />
    <HiddenField name="slug" />
</AutoForm>

// UDATING A DOCUMENT
<ModifierForm
    ref={ref => formRef = ref}
    schema={Schemas.MySchema}
    onSubmit={doc => this.handleSubmit(doc, formRef)}
    model={this.props.someDocument}
>
    <TextField name="title" />
    <HiddenField name="slug" />
</ModifierForm>

onSubmit calls these methods:

// INSERT
const InsertDocument = new ValidatedMethod({
  name: 'document.new',
  validate: Schemas.MySchema.validator({ clean: true }),
  run(doc) {
    if (!Meteor.userId()) {
      throw new Meteor.Error('document.new.unauthorized',
        'I am sorry, Dave. I am afraid I cannot let you do that.');
    }
    return MyCollection.insert(doc);
  }
});

//UPDATE
const UpdateDocument = new ValidatedMethod({
    name: 'document.update',
    validate({ doc }) {
        const errors = [];
        const validationContext = Schemas.MySchema.newContext();
        validationContext.validate(doc, {modifier: true })
        if (!validationContext.isValid()) {
            errors.push({name: "Validation Error"});
        }
        if (errors.length) {
          throw new ValidationError(errors);
        }
    },
    run({documentID, doc}) {
        if (!Meteor.userId()) {
          throw new Meteor.Error('document.update.unauthorized',
            'I am sorry, Dave. I am afraid I cannot let you do that.');
        }
        if (MyCollection.update({_id: documentID}, doc)) {
            return documentID;
        }
  }
});

When submitting one of the above forms, I get the error: Match error: Missing key 'type' in field [0]
Basically, I don't know when should the slug field be "populated", and how. Is there a way to do this without using the <HiddenField> component? What am I missing? How can I insert / update the document's slug, based on the title field, when submitting / validating the form?

Thank you,
R.

question

All 7 comments

I don't think HiddenField is needed in here - you can remove it. It is working OK during the insert I guess - you are calling clean there _(within .validator)_ but not during the update. Try to use .clean on your doc in update.

Indeed, this works fine for inserting.

I've changed the UpdateDocument method, adding

validationContext.clean(doc);

so the UpdateDocument method now looks like this:

//UPDATE
const UpdateDocument = new ValidatedMethod({
    name: 'document.update',
    validate({ doc }) {
        const errors = [];
        const validationContext = Schemas.MySchema.newContext();
        validationContext.clean(doc);
        validationContext.validate(doc, {modifier: true })
        if (!validationContext.isValid()) {
            errors.push({name: "Validation Error"});
        }
        if (errors.length) {
          throw new ValidationError(errors);
        }
    },
    run({documentID, doc}) {
        if (!Meteor.userId()) {
          throw new Meteor.Error('document.update.unauthorized',
            'I am sorry, Dave. I am afraid I cannot let you do that.');
        }
        if (MyCollection.update({_id: documentID}, doc)) {
            return documentID;
        }
  }
});

This works fine for documents which have already the slug field defined (inserted after adding this functionality), but I can't seem to make it work for older documents. When I try to update a document that doesn't have a slug yet, I get: Match error: Missing key 'type' in field [0]
Bump. :(

You should take a look at this document - that error suggest it's invalid.

I've found a workaround using this:

const model = Schemas.MySchema.clean(this.props.someDocument);

<ModifierForm
    ref={ref => formRef = ref}
    schema={Schemas.MySchema}
    onSubmit={doc => this.handleSubmit(doc, formRef)}
    model={model}
>

Would be great, however, to figure out how to do it without calling .clean on the retrieve document before, so forcing the slug to get a value even when there is none in the document retrieved from the database.

I see only two options: either the model from the db and/or schema are invalid or ModifierForm handles such cases incorrectly. If you'll look at it _(I assume it's the same as in documentation)_, it moves empty keys to $unset, which is rather incorrect in this case. Try to work with it.

Surely, meddling with the ModifierForm did the trick.
Not 100% most elegant solution, but it works as it should.

I replaced:
const remove = keys.filter(key => doc[key] === undefined);
with
const remove = keys.filter(key => (doc[key] === undefined && key !== 'slug'));
in the ModifierForm.

This, together with the autoValue function in the schema and the clean in the methods, is what got me what I needed: a slug generated automatically from the title field in a schema :)

Thank you for the help!

For more elegant solution you can add a custom field to SimpleSchema and mark them directly in the schema, like:

SimpleSchema.extendOptions(['computed']);

const MySchema = new SimpleSchema({
    slug: {type: String, computed: true, autoValue () {/*...*/}}
});

And in the ModifierForm:

const remove = keys.filter(key => (doc[key] === undefined && !schema.schema(key).computed);
Was this page helpful?
0 / 5 - 0 ratings