Home

Awesome

Build Status Backers on Open Collective Sponsors on Open Collective

AutoForm

AutoForm is a Meteor package that adds UI components and helpers to easily create basic forms with automatic insert and update events, and automatic reactive validation. Versions 6+ of this package require that you separately install the simpl-schema NPM package. Prior versions require and automatically install the simple-schema Meteor package. You can optionally use it with the collection2 package, which you have to add to your app yourself.

NOTE: AutoForm 6.0

AutoForm 6.0 is now available and requires switching your app to using the SimpleSchema package from NPM. Be sure to check out the change log for full details. Note that if you use add-on packages that haven't been updated yet, you will not yet be able to update to version 6.

Add-on Package Authors: Please test your package against AutoForm 6.0, and then release a major version update in which you change your api.use to api.use('aldeed:autoform@6.0.0');. I do NOT recommend using something like api.use('aldeed:autoform@4.0.0 || 5.0.0 || 6.0.0'); to try to support multiple major versions of AutoForm because there is currently a known Meteor issue where trying to support too many dependency paths leads to running out of memory when trying to resolve dependencies.

Table of Contents

<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

Table of Contents generated with DocToc

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Installation

In a Meteor app directory, enter:

$ meteor add aldeed:autoform

Also install SimpleSchema NPM package separately (AutoForm 6+):

$ npm i --save simpl-schema

And then also extend SimpleSchema to allow the autoform option in your schemas, if you plan to use it:

import SimpleSchema from 'simpl-schema';
SimpleSchema.extendOptions(['autoform']);

Community Add-On Packages

Submit a pull request to add your package to this list!

Custom Input Types

Dates and times:

Selects:

WYSIWYGs:

Markdowns:

Autocompletes:

Files:

Maps:

Ranges/Sliders:

Payments

Other:

Themes

Admin Panels

Content Management Systems

Components

Other

Demo

Live

Source

Example

Let's say you have the following Mongo.Collection instance, with schema support provided by the collection2 package. (Adding autoform to your app does not add collection2 by default so you need to run meteor add aldeed:collection2@3.0.0 for this example to work.)

Books = new Mongo.Collection("books");
Books.attachSchema(new SimpleSchema({
  title: {
    type: String,
    label: "Title",
    max: 200
  },
  author: {
    type: String,
    label: "Author"
  },
  copies: {
    type: Number,
    label: "Number of copies",
    min: 0
  },
  lastCheckedOut: {
    type: Date,
    label: "Last date this book was checked out",
    optional: true
  },
  summary: {
    type: String,
    label: "Brief summary",
    optional: true,
    max: 1000
  }
}, { tracker: Tracker }));

Be sure to define proper insert security for untrusted code if you've removed the insecure package. Call allow/deny or use ongoworks:security.

A Basic Insert Form

<template name="insertBookForm">
  {{> quickForm collection="Books" id="insertBookForm" type="insert"}}
</template>

That's it! This gives you:

A Basic Update Form

An update form is similar to an insert form, except you need to provide the document with the original values to be updated:

<template name="updateBookForm">
  {{> quickForm collection="Books" doc=this id="updateBookForm" type="update"}}
</template>

This example uses doc=this, assuming that you use something like iron:router's data function to set the template's data context to the book document. This is a common way to do it, but you could also use a helper function that returns the document.

Be sure to define proper update security for untrusted code if you've removed the insecure package. Call allow/deny or use ongoworks:security.

A Custom Insert Form

If you want to customize autogenerated forms for all forms, you can easily do so by writing your own templates. Refer to the templates section. However, sometimes a certain form has a complex schema or unique UI requirements, in which case you can use autoForm rather than quickForm, allowing you to define fields individually.

Here's an example:

<template name="insertBookForm">
  {{#autoForm collection="Books" id="insertBookForm" type="insert"}}
    <fieldset>
      <legend>Add a Book</legend>
      {{> afQuickField name='title'}}
      {{> afQuickField name='author'}}
      {{> afQuickField name='summary' rows=6}}
      {{> afQuickField name='copies'}}
      {{> afQuickField name='lastCheckedOut'}}
    </fieldset>
    <button type="submit" class="btn btn-primary">Insert</button>
  {{/autoForm}}
</template>

In this example, we added rows=6 to the "summary" field, which will cause it to be rendered as a textarea instead of a normal text input field.

Another Custom Insert Form

In the previous example of a custom insert form, we saw how afQuickField can be used to render a field with simple customizations. Now let's say we need to fully customize one of the fields. To do this, you can use the following more specific templates and helpers:

Here's an example:

<template name="insertBookForm">
  {{#autoForm collection="Books" id="insertBookForm" type="insert"}}
  <fieldset>
    <legend>Add a Book</legend>
    {{> afQuickField name='title'}}
    {{> afQuickField name='author'}}
    {{> afQuickField name='summary' rows=6}}
    {{> afQuickField name='copies'}}
    {{> afQuickField name='lastCheckedOut'}}
    <div class="form-group{{#if afFieldIsInvalid name='cost'}} has-error{{/if}}">
      <div class="input-group">
        <span class="input-group-addon">$</span>
        {{> afFieldInput name='cost'}}
        <span class="input-group-addon">/each</span>
      </div>
      {{#if afFieldIsInvalid name='cost'}}
      <span class="help-block">{{afFieldMessage name='cost'}}</span>
      {{/if}}
    </div>
  </fieldset>
  <button type="submit" class="btn btn-primary">Insert</button>
  {{/autoForm}}
</template>

We added a cost field to our form and customized it to display as an input group with add-ons.

Component and Helper Reference

NOTE: The afDeleteButton component that used to be part of autoform is now available as a separate package.

autoForm

Use this component as a block instead of <form> elements to wrap your form and gain all the advantages of the autoform package.

The following attributes are recognized:

quickForm

Use this component to generate an entire form in one line. It takes and requires all the same attributes as autoForm. In addition, it recognizes the following attributes:

Any other attributes you specify will be output as attributes of the <form> element, just like when using the autoForm component. When providing a boolean attribute, set it to true (no quotation marks) or a helper that returns true.

See this demo for examples of what happens when you specify various types of fields in the fields or omitFields attributes.

afFieldInput

Renders an input control for the field. The type of control depends on what you set the type attribute to. All of the HTML5 input types plus a few more are built in. Here is the full list of included input types:

There are add-on packages that provide additional input types (widgets, UI controls).

If you don't include a type attribute, the following logic is used to automatically select an appropriate type:

The following attributes are recognized:

Here's an example of passing options to generate a select field:

html:

{{> afFieldInput name="year" options=yearOptions}}

client.js:

Template.registerHelper("yearOptions", function() {
    return [
        {label: "2013", value: 2013},
        {label: "2014", value: 2014},
        {label: "2015", value: 2015}
    ];
});

Alternatively, you can specify options as an object with {value: label} format. Values are coerced into the expected type.

Template.registerHelper("yearOptions", function() {
    return {
      2013: "2013",
      2014: "2014",
      2015: "2015"
    };
});

You can also mix in optgroups. See the demo.

afFieldMessage

Accepts and requires just one attribute, name, which is the name of the schema key.

Outputs the user-friendly invalid reason message for the specified property, or an empty string if the property is valid. This value updates reactively whenever validation is performed. Refer to the SimpleSchema documentation for information on customizing the messages.

afFieldIsInvalid

Accepts and requires just one attribute, name, which is the name of the schema key.

Returns true if the specified key is currently invalid. This value updates reactively whenever validation is performed.

afFormGroup

Just as quickForm renders a form in one line, afFormGroup renders a form group, that is, everything related to a single field -- the label, the input, and the error message -- in one line.

This component accepts the same attributes as afFieldInput. Attributes that are prefixed with formgroup- become attributes on the div element, which contains the label and the field. Attributes that are prefixed with label- become attributes on the rendered label element while any remaining attributes are forwarded to the afFieldInput component. You can also set label=false to omit the label element or set label to a string to use that text as the label text.

afQuickField

The afQuickField component is a helper component that decides whether a particular key should be rendered using afArrayField, afObjectField, or afFormGroup, and then forwards all your attributes to the chosen component.

Refer to the "Objects and Arrays" section for additional information.

afQuickField Examples

{{> afQuickField name='firstField' autofocus=''}}
{{> afQuickField name='weirdColors' style="color: orange" label-style="color: green"}}
{{> afQuickField name="longString" rows=5}}
{{> afQuickField name="radioBoolean" type="boolean-radios" trueLabel="Yes" falseLabel="No"}}
{{> afQuickField name="selectBoolean" type="boolean-select" trueLabel="Yes" falseLabel="No"}}
{{> afQuickField name="optionsButNoSelect" options=numSelectOptions noselect="true"}}
{{> afQuickField name="firstOptionSelect" firstOption="(Select Something)" options=numSelectOptions}}
{{> afQuickField name="decimal" step="0.01"}}

afFieldValue

Displays the current value of a field

{{afFieldValue name='someField'}}

afFieldValueIs and afFieldValueContains

Use this helper with #if to dynamically show and hide sections of a form based on the current value of any non-array field on the form.

See the demo

afFieldNames

Use with #each to loop through all the field names for the form's schema or for an object field.

The following is roughly equivalent to a quickForm:

{{#autoForm}}
  {{#each afFieldNames}}
    {{> afQuickField name=this.name options=afOptionsFromSchema}}
  {{/each}}
{{/autoForm}}

Or you can provide a name attribute for an object field:

{{#autoForm}}
  {{#each afFieldNames name="profile"}}
    {{> afQuickField name=this.name options=afOptionsFromSchema}}
  {{/each}}
{{/autoForm}}

You can optionally pass fields or omitFields attributes to afFieldNames.

afQuickFields

Render an afQuickField for each field in the form schema or an object field.

{{#autoForm}}
  {{! These do the same thing}}
  {{#each afFieldNames name="profile"}}
    {{> afQuickField name=this.name options=afOptionsFromSchema}}
  {{/each}}
  {{> afQuickFields name="profile"}}
{{/autoForm}}

You can optionally pass fields or omitFields attributes to afQuickFields.

Objects and Arrays

Fields with type Object or Array are treated specially.

afObjectField

When you use the afQuickField component for a field that is an Object, it is rendered using the afObjectField component unless you override the type or specify options. This happens by default when you use a quickForm for a schema that has a field of type Object.

The afObjectField component renders all of an object field's subfields together as one group. The group is labeled with the name of the object field. The actual visual representation of the group will vary based on which theme template you use. For the "bootstrap3" default template, the group appears in a panel with a heading.

afArrayField

When you use the afQuickField component for a field that is an Array, it is rendered using the afArrayField component unless you override the type or specify options. This happens by default when you use a quickForm for a schema that has a field of type Array.

The afArrayField component renders all of an array field's array items together as one group. The group is labeled with the name of the array field. The actual visual representation of the group will vary based on which theme template you use. For the "bootstrap3" default template, the group appears in a panel with a heading.

Additionally, buttons for adding and removing array items are automatically added to the UI. This is also done by the template, which means that you can easily make your own "afArrayField" template if you don't like the default appearance of the add/remove buttons.

An afArrayField (or an afQuickField for an array) supports the additional attributes minCount and maxCount. Normally, by default, you cannot remove items below the schema-defined minCount and you cannot add items above the schema-defined maxCount. However, sometimes you don't want a minimum or maximum count defined in the schema, but you do want to limit the number of items on a certain form. To do this, use the minCount and maxCount attributes. Note, however, that you may not override the minCount to be less than the schema-defined minCount, and you may not override the maxCount to be more than the schema-defined maxCount.

An afArrayField (or an afQuickField for an array) also supports the initialCount attribute. Use it to override the default initial count to be something other than 1, including 0. Note that minCount will still always take precedence. That is, if the minCount is 1 and you specify initialCount=0, the initial count will be 1.

To specify options for each item in the array you can set

'arrayFieldName.$': {
  ...
  autoform: {
    afFieldInput: {
      options: function () {
        //return options object
      }
    }
  }
}

At the moment, the add and remove buttons disappear when you can't use them. This could be changed to make them disabled. You can do this yourself with a custom template, but if you have thoughts about how it should work out of the box, submit an issue to discuss.

afEachArrayItem

This is a block helper that can be used to render specific content for each item in an array. It tracks the addition and removal of array item fields (or groups of fields) reactively for you. This allows you to customize the repeated array fields, removal buttons, etc. It's generally most useful within a custom afArrayField template. See the built-in afArrayField templates for example usage.

afEachArrayItem supports the same attributes as afArrayField.

afArrayFieldIsFirstVisible and afArrayFieldIsLastVisible

These helpers must be used within an afEachArrayItem block and will return true or false depending on whether the current item/field in the array is the first or last visible item, respectively.

Form Types

Depending on the type attribute you specify on your quickForm or autoForm, your form will have different behavior when rendering, validating, and submitting. The following form types are built in to the package, but you may also define your own.

insert

Generates a document and inserts it on the client. You must provide a collection attribute referencing the Mongo.Collection instance. If the collection has an attached schema, it will be used for validation. If you provide a schema attribute, that schema will be used for validation, but the document must validate against the collection's schema, too.

update

Updates a document on the client. You must provide a collection attribute referencing the Mongo.Collection instance. If the collection has an attached schema, it will be used for validation. If you provide a schema attribute, that schema will be used for validation, but the document must validate against the collection's schema, too.

The form will generate and validate an update modifier. You must specify a doc attribute referencing the current document, which must have an _id property. Any properties present in doc will be used as the default values in the form fields.

update-pushArray

Updates a document on the client by adding the form document to an array within the larger document. You must provide a collection attribute referencing the Mongo.Collection instance. If the collection has an attached schema, it will be modified to be scoped appropriately and that new schema will be used for validation. If you provide a schema attribute, that schema will be used for validation, but the document must validate against the collection's schema, too.

You can think of this as an insert form for subdocuments. It generates and validates a document instead of a modifier, pretending that the array item schema is the full schema. Then it performs an update operation that does a $push of that document into the array.

Use the scope attribute on your form to define the array field into which the resulting document should be pushed. For example, scope="employees" or scope="employees.0.addresses".

method

Will call the server method with the name you specify in the meteormethod attribute. Passes a single argument, doc, which is the document resulting from the form submission.

You may optionally specify a DDP Connection in the ddp attribute. If you do, the method will be called using the DDP connection provided.

The method is not called until doc is valid on the client.

You must call check() in the method or perform your own validation since a user could bypass the client side validation.

method-update

Will call the server method with the name you specify in the meteormethod attribute. Your method will be called with a single object argument with _id and modifier properties.

You may optionally specify a DDP Connection in the ddp attribute. If you do, the method will be called using the DDP connection provided.

The method is not called until modifier is valid on the client.

You must call check() in the method or perform your own validation since a user could bypass the client side validation. Using the mdg:validated-method package is recommended.

normal

Will call any onSubmit hooks you define, where you can do custom submission logic. If onSubmit does not return false or call this.event.preventDefault(), the browser will also submit the form. This means that you can use AutoForm to generate and validate a form but still have it POST normally to an HTTP endpoint.

Example:

AutoForm.hooks({
  contactForm: {
    onSubmit: function (insertDoc, updateDoc, currentDoc) {
      if (customHandler(insertDoc)) {
        this.done();
      } else {
        this.done(new Error("Submission failed"));
      }
      return false;
    }
  }
});

The arguments passed to your onSubmit hook are as follows:

In addition to the normal this hook context, there is a this.done() method, which you must call when you are done with your custom client submission logic. This allows you to do asynchronous tasks if necessary. You may optionally pass arguments. If you pass an Error object, then any onError hooks will be called; otherwise, any onSuccess hooks will be called. The onSuccess hook has formType and result parameters, so calling this.done(null, "foo") will set the result to "foo".

If you return false, no further submission will happen, and it is equivalent to calling this.event.preventDefault() and this.event.stopPropagation(). If you return anything other than false, the browser will submit the form.

If you use autoValue or defaultValue options, be aware that insertDoc and updateDoc will not yet have auto or default values added to them. If you're passing them to insert or update on a Mongo.Collection with a schema, then there's nothing to worry about. But if you're doing something else with the object on the client, then you might want to call clean to add the auto and default values:

AutoForm.hooks({
  peopleForm: {
    onSubmit: function (doc) {
      PeopleSchema.clean(doc);
      console.log("People doc with auto values", doc);
      this.done();
      return false;
    }
  }
});

If you're sending the objects to the server in any way, it's always best to wait to call clean until you're on the server so that the auto values can be trusted.

disabled

All inputs will be disabled. Nothing happens when submitting.

readonly

All inputs will be read-only. Nothing happens when submitting.

Public API

For the full public API available on the AutoForm object, refer to the API documentation.

Non-Collection Forms

If you want to use an AutoForm for a form that does not relate to a collection (like a simple contact form that sends an e-mail), or for a form that relates to a collection that is schemaless (for example, Meteor.users()), you can do that.

  1. In client+server code, create a SimpleSchema instance to define the form's schema.
  2. Use the SimpleSchema instance as the schema attribute of autoForm or quickForm.
  3. Set up the form to be submitted properly. There are three ways to handle this:
    • Define an onSubmit hook for the form. Put your logic in that function and have it return false to prevent normal form submission.
    • Add normal form attributes like action and let the form do a normal browser POST after being validated.
    • Define a server method that does something with the form data. On your autoForm or quickForm set type="method" and meteormethod="yourServerMethodName".

When you use the third option, a server method, the form data will be gathered into a single object when the user clicks the submit button. Then that object will be cleaned and validated against the schema on the client and passed along to your method on the server. You must validate it again in your method on the server, using check() in combination with myAutoFormSchema. This is why we create the SimpleSchema instance in client+server code.

It's also generally best to call myAutoFormSchema.clean for the object again in the server method. In particular, you will definitely want to do this if the object's schema has auto or default values so that they can be added securely and accurately on the server.

An Example Contact Form

common.js:

Schema = {};
Schema.contact = new SimpleSchema({
    name: {
        type: String,
        label: "Your name",
        max: 50
    },
    email: {
        type: String,
        regEx: SimpleSchema.RegEx.Email,
        label: "E-mail address"
    },
    message: {
        type: String,
        label: "Message",
        max: 1000
    }
}, { tracker: Tracker });

Note that we've created an object Schema in which to store all of our app's schemas.

html:

<template name="contactForm">
  {{#autoForm schema=contactFormSchema id="contactForm" type="method" meteormethod="sendEmail"}}
  <fieldset>
    <legend>Contact Us</legend>
    {{> afQuickField name="name"}}
    {{> afQuickField name="email"}}
    {{> afQuickField name="message" rows=10}}
    <div>
      <button type="submit" class="btn btn-primary">Submit</button>
      <button type="reset" class="btn btn-default">Reset</button>
    </div>
  </fieldset>
  {{/autoForm}}
</template>

client.js

Template.contactForm.helpers({
  contactFormSchema: function() {
    return Schema.contact;
  }
});

server.js

Meteor.methods({
  sendEmail: function(doc) {
    // Important server-side check for security and data integrity
    check(doc, Schema.contact);

    // Build the e-mail text
    var text = "Name: " + doc.name + "\n\n"
            + "Email: " + doc.email + "\n\n\n\n"
            + doc.message;

    this.unblock();

    // Send the e-mail
    Email.send({
        to: "test@example.com",
        from: doc.email,
        subject: "Website Contact Form - Message From " + doc.name,
        text: text
    });
  }
});

Note the call to check(), which will throw an error if doc doesn't match the schema. To reiterate, you must call check() in the method or perform your own validation since a user could bypass the client side validation. You do not have to do any of your own validation with collection inserts or updates, but you do have to call check() on the server when submitting to a Meteor method.

Fine Tuning Validation

To control when and how fields should be validated, use the validation attribute on autoform or quickForm. Supported values are:

Notes:

Manual Validation

In addition to telling your form to validate on certain events, sometimes you need to manually validate.

Resetting Validation

After a successful submission, validation is reset, ensuring that any error messages disappear and form input values are correct. However, you may need to reset validation for other reasons, such as when you reuse an edit form to edit a different document. To do this, call AutoForm.resetForm(formId).

The Form Document

When the user submits an autoForm, an object that contains all of the form data is automatically generated. If the submission action is insert or a method call, this is a normal document object. If the submission action is update, this is a mongo modifier with $set and potentially $unset objects. In most cases, the object will be perfect for your needs. However, you might find that you want to modify the object in some way. For example, you might want to add the current user's ID to a document before it is inserted. You can use "before", "formToDoc", or "formToModifier" hooks to do this.

Getting Current Field Values

You can get the current values of all fields on a form at any time by passing the form id to AutoForm.getFormValues. This method is not reactive. The form must be currently rendered for this to work.

You can get the current value of a specific field on a specific form by passing the field name to AutoForm.getFieldValue. This method is reactive so it can be used in place of the built-in afFieldValueIs helper to show pieces of a form based on custom criteria about the values of other fields on the form. If using outside of the autoForm, pass the formId as the second argument.

Callbacks/Hooks

To add client-side hooks and callbacks for a form, use the AutoForm.hooks or AutoForm.addHooks method. The syntax for AutoForm.hooks is

AutoForm.hooks({
  myFormId: hooksObject
});

If you want to add the same hook for multiple forms or for all forms, use the AutoForm.addHooks method instead:

// Pass an array of form IDs for multiple forms
AutoForm.addHooks(['form1', 'form2', 'form3', 'form4'], hooksObject);

// Or pass `null` to run the hook for all forms in the app (global hook)
AutoForm.addHooks(null, hooksObject);

// Pass `true` as optional third argument to replace all existing hooks of the same type
AutoForm.addHooks('form1', hooksObject, true);

These calls should be anywhere in top-level client code and do not need to be within Meteor.startup. You should not put them in an autorun, template rendered function, or anywhere else where they will be called multiple times since that will cause the hooks to run multiple times for a single submission.

In all of the above examples, the hooks object should look like the following. This shows all possible hooks, but your object would have only the hooks that you need:

var hooksObject = {
  before: {
    // Replace `formType` with the form `type` attribute to which this hook applies
    formType: function(doc) {
      // Potentially alter the doc
      doc.foo = 'bar';

      // Then return it or pass it to this.result()
      //return doc; (synchronous)
      //return false; (synchronous, cancel)
      //this.result(doc); (asynchronous)
      //this.result(false); (asynchronous, cancel)
    }
  },

  // The same as the callbacks you would normally provide when calling
  // collection.insert, collection.update, or Meteor.call
  after: {
    // Replace `formType` with the form `type` attribute to which this hook applies
    formType: function(error, result) {}
  },

  // Called when form does not have a `type` attribute
  onSubmit: function(insertDoc, updateDoc, currentDoc) {
    // You must call this.done()!
    //this.done(); // submitted successfully, call onSuccess
    //this.done(new Error('foo')); // failed to submit, call onError with the provided error
    //this.done(null, "foo"); // submitted successfully, call onSuccess with `result` arg set to "foo"
  },

  // Called when any submit operation succeeds
  onSuccess: function(formType, result) {},

  // Called when any submit operation fails
  onError: function(formType, error) {},

  // Called every time an insert or typeless form
  // is revalidated, which can be often if keyup
  // validation is used.
  formToDoc: function(doc) {
    // alter doc
    // return doc;
  },

  // Called every time an update or typeless form
  // is revalidated, which can be often if keyup
  // validation is used.
  formToModifier: function(modifier) {
    // alter modifier
    // return modifier;
  },

  // Called whenever `doc` attribute reactively changes, before values
  // are set in the form fields.
  docToForm: function(doc, ss) {},

  // Called at the beginning and end of submission, respectively.
  // This is the place to disable/enable buttons or the form,
  // show/hide a "Please wait" message, etc. If these hooks are
  // not defined, then by default the submit button is disabled
  // during submission.
  beginSubmit: function() {},
  endSubmit: function() {}
};

The following properties and functions are available in all submission hooks when they are called. This does not include formToDoc, formToModifier, or docToForm.

Notes:

formToDoc, formToModifier, and docToForm

Specify formToDoc/formToModifier and docToForm hooks if you need form values in a different format in your form versus in the mongo document. They are mainly useful if you decide to override an input type.

Unlike document modifications made in "before hooks", modifications made in the formToDoc hooks are made every time the form is validated, which could happen a couple times per second on the client, depending on validation mode. The other hooks are run only right before the corresponding submission actions, as their names imply.

Here is an example where this feature is used to allow comma-delimited entry in a text field but store (and validate) the values as an array:

First specify type: [String] in the schema. The schema should reflect what you are actually storing.

Add a type attribute to your afFieldInput component:

{{> afFieldInput name="tags" type="text"}}

Then in client code, add the hooks:

AutoForm.hooks({
  postsForm: {
    docToForm: function(doc) {
      if (_.isArray(doc.tags)) {
        doc.tags = doc.tags.join(", ");
      }
      return doc;
    },
    formToDoc: function(doc) {
      if (typeof doc.tags === "string") {
        doc.tags = doc.tags.split(",");
      }
      return doc;
    }
  }
});

Note that the update and method-update forms call formToModifier instead of formToDoc, and forms without a type attribute call both formToModifier and formToDoc. formToModifier is passed a Mongo modifier instead of a document.

Putting Field Attribute Defaults in the Schema

If you are using the quickForm, afQuickField, afFormGroup, afObjectField, or afArrayField components, you may find that you want to set an attribute on something generated within those templates. In most cases, you can do this by setting attribute defaults in the corresponding schema definition. Add an autoform property to the schema defininition, and set it to an object specifying attribute defaults. For example, to ensure a field is always rendered as a textarea, you could do this:

summary: {
  type: String,
  optional: true,
  max: 2000,
  autoform: {
    rows: 10
  }
}

You can also set autoform.omit to true in the schema definition for a field to prevent it from ever being included in quickForms, afObjectFields, afArrayFields, etc. This is useful for autoValue properties such as createdAt that you know you will not want on a form.

You can set the type for the field in the autoform object in the schema, too.

You can (and in most cases, should) target your attributes to a particular component by nesting them under the component name:

summary: {
  type: String,
  optional: true,
  max: 2000,
  autoform: {
    afFieldInput: {
      type: "textarea",
      rows: 10,
      class: "foo"
    }
  }
}

Tip: Any attribute can instead be provided as a function that returns the attribute's value.

You can pass data structures using the data property. They will not be used as attributes, instead they will be available in the field's context. For example:

summary: {
  type: String,
  autoform: {
    afFieldInput: {
      data: {
        someArray: ['apple', 'orange', 'banana'],
        someObj: {
          complex: {
            data: 'structure'
          }
        }
      }
    }
  }
}

Complex Schemas

You can use mongo dot notation to map an input to a subdocument. For example:

Dates

By default, a schema field with type: Date will generate an input element with type=date. You can override the type on each afFieldInput or afQuickField helper if you want to use a datetime or datetime-local input instead. Here are some tips for using each input type.

Consider using the moment and moment-timezone libraries to make this easy.

type=date

type=datetime

Note: Using this type of input requires that the user do all the work to convert from the applicable time zone to the UTC time zone since the entered time is assumed to be UTC. It's generally better to use datetime-local.

type=datetime-local

Theme Templates

AutoForm has a robust and extendable template system. The following templates are built in:

Using a Different Template

The AutoForm components can be generated using a specific template by providing a template name as the template option. In addition, you can change the default template for all components or for a particular component type at any time:

AutoForm.setDefaultTemplate('plain');

//OR

AutoForm.setDefaultTemplateForType('quickForm', 'plain-fieldset');

These methods are reactive, meaning that as soon as you call them, you'll instantly see all visible forms change, if they're using the defaults.

Here's the list of possible types you can use for the first argument of setDefaultTemplateForType:

Creating a Custom Template

To define a custom template that is recognized by the AutoForm templates system, simply create a template with the name afType + "_" + templateName. For example, if I want to define a template named "nothing" that can be used by a quickForm to generate nothing:

<template name="quickForm_nothing">
</template>

And then use it like this:

{{> quickForm schema=mySchema id="nothingForm" template="nothing"}}

Or tell all quickForms to use it:

AutoForm.setDefaultTemplateForType('quickForm', 'nothing');

Obviously a real example would be a bit more complex. Your template will have access to certain built-in helpers and properties in the data context, and you can use those to generate what you need in the format you need it. In practice, it is easiest to start by duplicating one of the built-in templates and then modify your copy as necessary.

If you create a good set of templates for a commonly used framework or a common purpose, consider releasing it as a separate add-on package. The goal is to keep the built-in templates minimal but to provide many others through separate packages.

Grouping Fields

The "plain", "bootstrap", and "bootstrap-horizontal" quickForm templates allow you to group fields into fieldsets in the schema if you want.

For example if you add this to several of the fields in your form schema:

{
  autoform: {
    group: 'Contact Information'
  }
}

Then all of those fields will be grouped into a fieldset with a legend that says "Contact Information". The fieldsets appear below any fields that do no list a group.

This only affects quickForms.

The fieldset has class "af-fieldGroup" and the legend has class "af-fieldGroup-heading" to help with styling.

The Telescope app makes use of this feature. Thanks to @SachaG for contributing it.

Sticky Validation Errors

Every time AutoForm revalidates your form, it overwrites the list of invalid fields for that form. This means that adding your own errors to the form validation context (using the SimpleSchema API) will not always work because your custom errors will disappear upon first revalidation. To solve this, you can add sticky errors for a form. Sticky errors do not go away unless you reset the form, the form instance is destroyed, or you manually remove them.

Defining Custom Input Types

Making a custom input type (form widget) is easy.

  1. Create a template and any necessary helpers for it.
  2. Call AutoForm.addInputType to give your new type a name and provide a few other necessary details.

AutoForm.addInputType accepts two arguments: name and options. The name argument defines the string that will need to be used as the value of the type attribute for afFieldInput. The options argument is an object with some of the following properties:

It's possible to use template helpers instead of valueIn and contextAdjust, but by keeping template helpers to a minimum, you make it easier for someone to override the theme template and still use your custom input type. For example, the bootstrap3 template overrides some of the default input types to add classes and adjust markup a bit, but it does not need to redefine template helpers to make context adjustments since valueIn and contextAdjust do that.

There is nothing overly special about the HTML template you define. Check out the properties of this within the template to get all of the information you need to render your control. Primarily you need to use this.value to set the control's value and the provided attributes in this.atts should be passed along to one or more of the elements you generate. In particular, you must make sure that the data-schema-key attribute in this.atts is added to one of the generated elements, the one that you want to be provided as this in your valueOut function.

For more examples, see the built-in input types here.

Common Questions

Should the value of schema and collection have quotation marks around it?

It depends. If you use quotation marks, then you are telling the autoform to "look for an object in the window scope with this name". So if you define your collections at the top level of your client files and without the var keyword, then you can use this trick to avoid writing helpers.

If you don't use quotation marks, then you must define a helper function with that name and have it return the SimpleSchema or Mongo.Collection instance.

Probably the best technique for organizing your form schemas and making them available as helpers is to add all SimpleSchema instances to a Schemas object and register that object as a helper:

common.js:

Schemas = {};

Schemas.ContactForm = new SimpleSchema({
  name: {
    type: String,
    label: "Your name",
    max: 50
  },
  email: {
    type: String,
    regEx: SimpleSchema.RegEx.Email,
    label: "E-mail address"
  },
  message: {
    type: String,
    label: "Message",
    max: 1000
  }
}, { tracker: Tracker });

//... define all schemas

client.js:

Template.registerHelper("Schemas", Schemas);

html:

<template name="contactForm">
  {{#autoForm schema=Schemas.ContactForm id="contactForm" type="method" meteormethod="sendEmail"}}
  <!-- etc. -->
  {{/autoForm}}
</template>

Which components should I use?

Generally you should start by using a quickForm for every form your app needs. Then, if the generated form does not look correct, try the following in this order:

  1. If there is something you can change or fix about the form's schema that will cause the quickForm to render correctly, change it. If the form is for a collection, you can also try using a different schema (a subset, perhaps with stricter rules) to render the form by supplying the schema attribute in addition to the collection.
  2. If many of your forms need the same change, try writing and using a custom template for those forms if possible.
  3. Switch to using an autoForm with afQuickFields. You can then set attributes for both inputs and labels (using label- prefix), omit labels if necessary (label=false), and specify field-specific template overrides. These features are typically enough to get the appearance you want.
  4. If you really need something truly custom for just one field in just one form, you can then switch to using the afFieldInput component directly.

Can I reuse the same quickForm or autoForm for both inserts and updates?

Yes. Your code that flips between states should do the following in this order:

  1. Change the type attribute's value to "insert" or "update" as appropriate, probably by updating a reactive variable.
  2. Change the doc attribute's value to the correct document for an update or to null (or a document containing default values) for an insert, probably by updating a reactive variable.
  3. Call AutoForm.resetForm(formId). This will clear any existing validation errors for the form.

How can I show an asterisk after the label for required fields?

Beginning with AutoForm 5.0, a data-required attribute is now present on the form group div element for all built-in afFormGroup templates if the field is required. This allows you to use css like the following to display an asterisk after required fields: [data-required] label:after {content: '*'}

What are the various ways I can specify options for a select, radio group, or checkbox group?

To specify options for any field, use the options attribute and provide an array of objects, where each object has a label property and a value property. There are several different ways you can do this.

Alternatively, you can specify options as an object with {value: label} format. Values are coerced into the expected type.

Use allowed values array from the schema as both the label and the value

{
  favoriteColor: {
    type: String,
    allowedValues: ['red', 'green', 'blue']
  }
}
{{> afQuickField name="favoriteColor" options="allowed"}}

Fields generated by quickForm or afObjectField or afArrayField use allowedValues by default.

Set options in the schema

{
  favoriteColor: {
    type: String,
    allowedValues: ['red', 'green', 'blue'],
    autoform: {
      options: [
        {label: "Red", value: "red"},
        {label: "Green", value: "green"},
        {label: "Blue", value: "blue"}
      ]
    }
  }
}

Alternative syntax:

{
  favoriteColor: {
    type: String,
    allowedValues: ['red', 'green', 'blue'],
    autoform: {
      options: {
        red: "Red",
        green: "Green",
        blue: "Blue"
      }
    }
  }
}
{{> afQuickField name="favoriteColor"}}

Since autoform.options is in the schema, that will be used instead of allowedValues.

Calculate options in the schema

{
  favoriteColor: {
    type: String,
    allowedValues: ['red', 'green', 'blue'],
    autoform: {
      options: function () {
        return _.map(['red', 'green', 'blue'], function (c, i) {
          return {label: "Color " + i + ": " + c, value: c};
        });
      }
    }
  }
}
{{> afQuickField name="favoriteColor"}}

autoform.options can be an array or a function that returns an array.

Use a helper

{
  favoriteColor: {
    type: String
  }
}

Template.myFormTemplate.helpers({
  colorOptions: function () {
    return Colors.find().map(function (c) {
      return {label: c.name, value: c._id};
    });
  }
});
{{> afQuickField name="favoriteColor" options=colorOptions}}

This example provides a reactive list of colors options read from the Colors collection. This assumes that you've created and populated the Colors collection and published the necessary colors (documents) to the client. Although using a UI helper is ideal because it is reactive, it can't be done with a quickForm or afObjectField or afArrayField, unless you make a custom template.

Can I put HTML in my error messages?

Yes. Define your messages using one of the SimpleSchema methods, including HTML elements such as <strong>. Then be sure to wrap your afFieldMessage tags in triple stashes.

Examples

I would like to link to examples of public sites using this in production. If you have one, please add a link here. You can include a brief description of how you're using autoforms on the site, too. If the code is publicly available, link to that, too.

Troubleshooting

Contributing

More Info

Contributors

This project exists thanks to all the people who contribute. [Contribute]. <a href="graphs/contributors"><img src="https://opencollective.com/autoform/contributors.svg?width=890" /></a>

Backers

Thank you to all our backers! 🙏 [Become a backer]

<a href="https://opencollective.com/autoform#backers" target="_blank"><img src="https://opencollective.com/autoform/backers.svg?width=890"></a>

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [Become a sponsor]

<a href="https://opencollective.com/autoform/sponsor/0/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/0/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/1/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/1/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/2/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/2/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/3/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/3/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/4/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/4/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/5/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/5/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/6/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/6/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/7/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/7/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/8/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/8/avatar.svg"></a> <a href="https://opencollective.com/autoform/sponsor/9/website" target="_blank"><img src="https://opencollective.com/autoform/sponsor/9/avatar.svg"></a>