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.
AutoForm 7.0 is now available and decouples from the default themes. You will have to install themes manually!
Be sure to check out the change log for full details, first. 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 7.0, and then release a major version update in which you change your api.use
to api.use('aldeed:[email protected]');
.
We do NOT recommend using something like api.use('aldeed:[email protected] || 7.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 generated with DocToc
schema
and collection
have quotation marks around it?quickForm
or autoForm
for both inserts and updates?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']);
By default there is no theme included, so you will have to install a theme, too. The following themes are available and tested to work with v7:
Please also consider, that twbs:bootstrap
is depending on an outdated (and potential insecure!) Bootstrap version.
Better: Use the latest Bootstrap 3.x or 4.x from NPM in favour.
If you update to 7.0.0 you will likely encounter errors from your extension packages like
While selecting package versions:
error: Conflict: Constraint aldeed:[email protected] is not satisfied by aldeed:autoform 7.0.0.
Constraints on package "aldeed:autoform":
* aldeed:autoform@=7.0.0 <- top level
* aldeed:[email protected] <- someone:packagename x.y.z
You can easily circumvent this issue by editing the package entry in .meteor/packages
from aldeed:autoform
to aldeed:[email protected]!
(note the exclamation mark).
This is because many extensions will not have the 7.0.0
reference in their
package.js
file, yet. However, this major version is intended to not break
compatibility with extensions, that worked with 6.x
. If you encounter any
runtime issues with extensions, please open an issue.
If you come from a previous version and want to "keep things as they were" then this is the option you should choose.
AutoForm now comes only with the core functionality added to the initial package
code. In order to make the Templates available, too, you only need to
put the following line into your top-level client startup code (for example
imports/startup/client/autoform.js
):
import 'meteor/aldeed:autoform/static'
That's it. The Templates are now available.
This package supports dynamic-import
, which helps to reduce initial bundle
size of the package from ~110 KB to ~60 KB (estimated via bundle-visualizer
).
The following example shows how to import the packages dynamically:
import 'meteor/aldeed:autoform/dynamic'
AutoForm.load()
.then(() => {
// ... on init success code, for example set a ReactiveVar to true
))
.catch(e => {
// ... on error code
})
You can even combine this with one of the themes (if they support dynamic imports) like in the following example:
import { AutoFormThemeBootstrap4 } from 'meteor/communitypackages:autoform-bootstrap4/dynamic'
import 'meteor/aldeed:autoform/dynamic'
async function init () {
await AutoForm.load()
await AutoFormThemeBootstrap4.load()
// theme is imported, you can now make the form available
// you could use a reactive var that resolves to true here
// or any other mechanism you like to use to reactively activate the form
AutoForm.setDefaultTemplate('bootstrap4')
}
(function () {
init()
.catch(e => console.error('[autoForm]: init failed - ', e))
.then(() => console.info('[autoForm]: initialized'))
})()
Note, that you can't use the #autoForm
or >quickForm
Templates before the
import has not completed. You can however use a ReactiveVar
in your Temlate
to "wait" with rendering the form:
import { ReactiveVar } from 'meteor/reactive-var'
import { initAutoForm } from 'meteor/aldeed:autoform/dynamic'
const autoFormLoaded = new ReactiveVar()
initAutoForm()
.then(() => autoFormLoaded.set(true))
.catch(e => {
// ... on error code
})
// ... other Template code
Template.myCoolForm.helpers({
loadComplete() {
return autoFormLoaded.get()
}
})
Submit a pull request to add your package to this list!
Dates and times:
Selects:
WYSIWYGs:
Markdowns:
Autocompletes:
Files:
Maps:
Ranges/Sliders:
Payments
Other:
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:[email protected]
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.
<template name="insertBookForm">
{{> quickForm collection="Books" id="insertBookForm" type="insert"}}
</template>
That's it! This gives you:
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.
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.
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.
NOTE: The afDeleteButton
component that used to be part of autoform is now available as a separate package.
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:
collection
: Required if schema
is not set. Set to one of the following:
Mongo.Collection
that has a schema defined.Mongo.Collection
instance that haswindow
namespace.schema
: Required if collection
is not set. This schema will be used to generatecollection
if you want to use a schema that is slightly different fromSimpleSchema
.SimpleSchema
instance that is inwindow
namespace.id
: Required. This is used as the id
attribute on the rendered form
doc
: Required for an update form, and must have at least an _id
property. Pass the current documentfindOne()
for example. For an insert form,value
attribute on each field within the form).validation
: Optional. See the "Fine Tuning Validation" section.template
: Optional. See the "Templates" section.type
: Optional. The form type. Default if not provided is "normal". See Form Types.meteormethod
: Optional. When type
is "method" or "method-update", indicate the name of theddp
: Optional. When type
is "method" or "method-update", provide an alternative DDP Connection that should be used to call the Meteor method in this attribute.resetOnSuccess
: Optional. The form is automatically resetfalse
.autosave
: Optional. Set to true
to enable automatic form submission. Whenever the form change
event is emitted, the change will be automatically saved to the database.autosaveOnKeyup
: Optional. Set to true
to enable automatic form submission for a type="update
form on keyup
event. Whenever a keyup
event is emitted on a form field, the change will be automatically saved to the database (throttled to 500ms). It's best to set trimStrings=false
when using this option. If you don't, spaces may be deleted while typing.
filter
: Optional. Set to false
for an insert or update form to skip filtering out unknown properties when cleaning the form document.autoConvert
: Optional. Set to false
for an insert or update form to skip autoconverting property values when cleaning the form document.removeEmptyStrings
: Optional. Set to false
for an insert or update form to keep empty string values when cleaning the form document.trimStrings
: Optional. Set to false
for an insert or update form to keep leading and trailing spaces for string values when cleaning the form document.setArrayItems
: Optional. Set to true
for an update form that is updating specific array items. Note that there is a quirk of MongoDB that will create objects instead of arrays when this is set to true
, if there is not already an array in the database. So if you set this to true
, be sure that the corresponding array property is never null
or missing in the database. It must always be an array with 0 or more items.preserveForm
: Optional. Set to false
to disable preserving of form values across hot refreshes. This can sometimes help resolve issues with sticky form values.<form>
element, meaning that you can add classes, etc. When providing a boolean attribute, set it to true
(no quotation marks) or a helper that returns true
.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:
type
: Two additional type values are supported: "readonly" and "disabled".buttonClasses
: Set the class attribute for the rendered submit button. Some templates may provide a default class if you don't set this.buttonContent
: The submit button content. If you don't set this, "Submit" is used. If you set this to false
, no submit button is rendered.fields
: Optional. Bind an array or specify a comma-delimited string of fieldomitFields
: Optional. Bind an array or specify a comma-delimited string of fieldAny 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.
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:
options
attribute, a select
input is used. If your schema expects an array for the field, then it is a select-multiple
input. If you prefer radios or checkboxes (for example, if it is a short list of options), then add noselect=true
attribute or simply set the type
to select-checkbox
, select-checkbox-inline
, select-radio
, or select-radio-inline
.String
and you include the rows
attribute, a textarea
is used.Number
, a number
type is used.Date
, a date
type is used.Boolean
, the boolean-checkbox
type is used. You may want to specify a type
of boolean-radios
or boolean-select
instead. If you do so, use the trueLabel
, falseLabel
, and nullLabel
attributes to set the labels used in the radio or select control.text
type is used.The following attributes are recognized:
name
: Required. The name of the schema key this field is for.template
(default="bootstrap3"): Specify the name of a different built-in oroptions
: An array of options objects (see below). By specifying options, you cause the generated DOMselect
element with these options, unless you also usenoselect
. To use the allowedValues
from the schema as the options, setoptions="allowed"
. To specify a label to be displayed when there is nofirstOption="(My Select One Label)"
.firstOption
: Use with the options
attribute to specify a string to use for the first option of a select
input, which shows when nothing has been selected yet. For example, firstOption="(You Should Really Pick Something From This List)"
. There is a default first option "(Select One)". If you don't want any default option, then do firstOption=false
, but make sure your select
input has a default value
or this will result in a confusing UX where it looks like the first option is selected but it isn't.capitalize
: Used only when you've set options="allowed"
. Set this to true
allowedValues
.noselect
: Use in conjunction with options
attribute. Set this attributetrue
to render radios or checkboxes for the options
instead of select
.trueLabel
: Set to the string that should be used as the label for the true
boolean-select
or boolean-radios
.falseLabel
: Set to the string that should be used as the label for the false
boolean-select
or boolean-radios
.nullLabel
: Set to the string that should be used as the label for the empty value option for an input with type boolean-select
or boolean-radios
.value
: Set a specific, potentially reactive, value for the input. If you have also provided a doc
attribute on the autoForm
or quickForm
, this value will override the value from the doc
object.defaultValue
: Set a reactive default value for the input. If you have also provided a doc
attribute on the autoForm
or quickForm
, this value will be used only when the doc
object has no value for this field. This takes precedence over the defaultValue
property of the field's schema. (Also, defaultValue
from the schema is slightly different in that it is never used if you provide a doc
attribute.)class
, etc. When providing a boolean attribute, set it to true
(no quotation marks) or a helper that returns true
.placeholder
: As with other attributes, this will be passed along to theplaceholder="schemaLabel"
to use the field label defined in the schema asHere'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.
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.
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.
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.
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.
afQuickField
component for a field that is an Object
, it is rendered using the afObjectField
component unless you override the type
or supply options
.afQuickField
component for a field that is an Array
, it is rendered using the afArrayField
component unless you override the type
or supply options
.afFormGroup
component.Refer to the "Objects and Arrays" section for additional information.
{{> 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"}}
Displays the current value of a field
{{afFieldValue name='someField'}}
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
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
.
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
.
Fields with type Object
or Array
are treated specially.
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.
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.
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
.
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.
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.
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.
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.
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"
.
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.
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.
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:
insertDoc
: The form input values in a document, suitable for use with insert().updateDoc
: The form input values in a modifier, suitable for use with update().currentDoc
: The object that's currently bound to the form through the doc
attributeIn 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.
All inputs will be disabled. Nothing happens when submitting.
All inputs will be read-only. Nothing happens when submitting.
For the full public API available on the AutoForm
object, refer to the API documentation.
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.
SimpleSchema
instance to define the form's schema.SimpleSchema
instance as the schema
attribute of autoForm
or quickForm
.onSubmit
hook for the form. Put your logic in that function and have it return false
to prevent normal form submission.action
and let the form do a normal browser POST after being validated.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.
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: "[email protected]",
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.
To control when and how fields should be validated, use the validation
attribute on autoform
or quickForm
. Supported values are:
none
: Do not validate any form fields at any time.submit
: Validate all form fields only when the form is submitted.keyup
: Validate each form field on every key press and when the user movesblur
: Validate each form field when the user moves the cursor off itsubmitThenKeyup
: (Default) At first acts like the submit
option, but after thekeyup
.submitThenBlur
: At first acts like the submit
option, but after theblur
.browser
: Let the browser handle validation, if supported.Notes:
novalidate="novalidate"
attributekeyup
validation, there is sometimes a bug in Safari whereautocomplete="off"
attribute to your input fields.In addition to telling your form to validate on certain events, sometimes you need to manually validate.
AutoForm.validateField(formId, fieldName, skipEmpty)
. It returns true
or false
depending on the validity of the field's current value, and it also causes reactive display of any errors for that field in the UI.AutoForm.validateForm(formId)
. It returns true
or false
depending on the validityAfter 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)
.
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.
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.
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.
this.addStickyValidationError(key, type, [value])
: Calls AutoForm.addStickyValidationError
for the formthis.autoSaveChangedElement
: The input element that was changed to cause this form submission (if the submission was due to autosave)this.collection
: The collection attached to the form (from collection
attribute)this.currentDoc
: The current document attached to the form (from doc
attribute)this.docId
: The _id
attribute of the doc
attached to the form, if there is one, or for an type='insert'
form, the _id
of the newly inserted doc, if one has been inserted.this.event
: The browser submit eventthis.formAttributes
: The object containing all the form attributes from the autoForm
or quickForm
this.formId
: The form's id
attribute (useful in a global hook)this.insertDoc
: The gathered current form values, as a normal objectthis.removeStickyValidationError(key)
: Calls AutoForm.removeStickyValidationError
for the formthis.resetForm()
: Call this if you need to reset the formthis.ss
: The SimpleSchema instance used for validating the formthis.ssIsOverride
: This is true
if this.ss
is an override schema, meaning it's coming from a schema
attribute on the autoForm
or quickForm
, but there is also a collection
attribute pointing to a collection that has its own schema attached.this.template
: The autoForm
template instancethis.updateDoc
: The gathered current form values, as a mongo modifier object suitable for passing to a collection update
callthis.validationContext
: The validation context used for the form. You can use this to check or add (non-sticky) invalid keys.Notes:
hooks
or addHooks
multiple times. The list of hooks is extended each time you call it, which means that multiple hooks of the same type can run for the same form.before
hooks are called after the form is deemed valid but before the submission operation happens. (The submission operation depends on the form type.) These hooks are passed the document or modifier as gathered from the form fields. If necessary they can modify the document or modifier. These functions can perform asynchronous tasks if necessary. If asynchronicity is not needed, simply return the document or modifier, or return false
to cancel submission. If you don't return anything, then you must call this.result()
eventually and pass it either the document or modifier, or false
to cancel submission. This is run only on the client. Therefore, you should not assume that this will always run since a devious user could skip it.
after
hooks are similar to the callbacks for insert
and update
and method calls. They are passed two arguments: error
and result
formToDoc
, formToModifier
, and docToForm
hooks.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 (Array.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.
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'
}
}
}
}
}
}
You can use mongo dot notation to map an input to a subdocument. For example:
{{> afFieldInput 'address.street'}}
, whatever is entered in the field will be assigned to doc.address.street
.{{> afFieldInput 'addresses.1.street'}}
, whatever is entered in the field will be assigned to the street
property of the object at index 1 in the doc.addresses
array.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.
YYYY-MM-DD
,Date
object representing thatDate
objects to an update autoformtype="date"
, the date that will be used asDate
objectDate
objectmoment
library, you might do somethingmoment.utc(myDate).format("LL")
Date
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.
date string + "T" + time string + "Z"
, and it will be saved as a Date
object representing thatDate
objects to an update autoformtype="datetime"
, the date that will be used asDate
objectDate
objectmoment
library, you might do something like this: moment.utc(myDate).format("LLLL")
Date
type="datetime-local"
, the datetime string that is entered will be assumed to be in the client's local timezone. To use a different specific timezone, add a moment-timezone
package to your app and specify a timezoneId
attribute on the afFieldInput
or afFormGroup
helper.type="datetime-local"
in a formtimezoneId
attribute to the ID for this time zone.date string + "T" + time string
.)Date
objects to an update autoformtype="datetime-local"
, be sure to set thetimezoneId
attribute on the component to the time zone ID that applies. This willmoment(myDate).tz(timezoneId).format()
moment
and moment-timezone
packages/libraries.Date
AutoForm has a robust and extendable template system. The following templates are built in:
bootstrap3
: Default. UI elements will be generated using Bootstrap 3 structurebootstrap3-horizontal
: Can be used with afFormGroup
or afQuickField
or quickForm
only. Generates markup andtemplate="bootstrap3-horizontal"
on your afQuickField
, you must{{> afQuickField name="name" template="bootstrap3-horizontal" label-class="col-sm-3" input-col-class="col-sm-9"}}
or {{> quickForm schema=Schemas.ContactForm id="contactForm" type="method" meteormethod="sendEmail" template="bootstrap3-horizontal" label-class="col-sm-3" input-col-class="col-sm-9" id-prefix="my-prefix"}}
.plain
: UI elements will be generated with no particular UI framework in mind.plain-fieldset
: Can be used with quickForm
only. Wraps the form in afieldset
. Additionally allows a label
attribute that determines what theThe 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
:
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.
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.
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.
AutoForm.addStickyValidationError(formId, key, type, [value])
AutoForm.removeStickyValidationError(formId, key)
Making a custom input type (form widget) is easy.
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:
template
: Required. The name of the template to use, which you've defined in a .html
file.valueIn
: Optional. A function that adjusts the initial value of the field, which is then available in your template as this.value
. You could use this, for example, to change a Date
object to a string representing the date. You could also use a helper in your template to achieve the same result.valueOut
: Required. A function that AutoForm calls when it wants to know what the current value stored in your widget is. In this function, this
is the jQuery object representing the element that has the data-schema-key
attribute in your custom template. So, for example, in a simple case your valueOut
function might just do return this.val()
.valueConverters
: Optional. An object that defines converters for one or more schema types. Generally you will use valueOut
to return a value for the most common or logical schema type, and then define one or more converter functions here. The converters receive the valueOut
value as an argument and should then return either the same value or a type converted/adjusted variation of it. The possible converter keys are: "string", "stringArray", "number", "numberArray", "boolean", "booleanArray", "date", and "dateArray". Refer to the built-in type definitions for examples.contextAdjust
: Optional. A function that adjusts the context object that your custom template receives. That is, this function accepts an object argument, potentially modifies it, and then returns it. That returned object then becomes this
in your custom template. If you need access to attributes of the parent autoForm in this function, use AutoForm.getCurrentDataForForm()
to get them.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.
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>
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:
quickForm
to render correctly, change it. If the form is forschema
attribute incollection
.autoForm
with afQuickField
s. You can then setlabel-
prefix), omit labelslabel=false
), and specify field-specific template overrides.afFieldInput
component directly.quickForm
or autoForm
for both inserts and updates?Yes. Your code that flips between states should do the following in this order:
type
attribute's value to "insert" or "update" as appropriate,doc
attribute's value to the correct document for an update ornull
(or a document containing default values) for an insert, probably byAutoForm.resetForm(formId)
. This will clear any existing validationBeginning 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: '*'}
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.
{
favoriteColor: {
type: String,
allowedValues: ['red', 'green', 'blue']
}
}
{{> afQuickField name="favoriteColor" options="allowed"}}
Fields generated by quickForm
or afObjectField
or afArrayField
use allowedValues
by default.
{
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
.
{
favoriteColor: {
type: String,
allowedValues: ['red', 'green', 'blue'],
autoform: {
options: function () {
return ['red', 'green', 'blue'].map(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.
{
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.
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.
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.
AutoForm: no theme found, please consult the README.
.AutoForm.debug()
in your client code to enable extra logging.submit
.before
hook is called but the form is never submitted, make sure you are returning the doc
or modifier
from the hook or eventually calling this.result(doc)
if you're doing something asynchronous.MCP welcomes any form on contribution. If you are interested, please read more about it in the contributing guide and also consider the MCP Code of Conduct.
In order to improve development we have replaced TinyTest with
meteortesting:mocha
in combination with chai
and puppeteer
.
This makes local tests much easier and also allows us to run tests in the CI. We have added a minimal test project in this repo, that serves as our proxy environment for running the tests.
In order to run the tests you there need to do the following:
$ cd testapp
$ meteor npm install
$ meteor npm run lint
$ meteor npm run test
Test commands
The following test commands are available:
lint
- runs the JavaScript standard linterlint:fix
- runs the JavaScript standard linter and autofixes issuestest
- runs the tests once; CLI-onlytest:watch
- runs the tests in watch mode; CLI-onlyPublishing note
If you publish the package to atmosphere, make sure you remove the test project or move it outside of the package root.
Testing with coverage
Many thanks to Eric Dobbertin (aldeed) for creating this package and many years of improvement and for the trust in Meteor Community Packages.
Also many thanks to all the contributors.
This package is released under the MIT License. See the LICENSE file for more information.