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.
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);