Managed Forms
Managed Forms are a streamlined approach to website forms that reduce the many steps needed to process forms manually (TODO link to Enhanced Forms). It is designed for a form submission workflow where:
- the website user is anonymous (i.e. not logged in)
- the data collected is easily presented in a single page form
- an optional collection of files may be included
- the data may be validated against simple rules
- the submitter may be automatically verified as a non-robot using a CAPTCHA service (reCAPTCHA or hCAPTCHA)
- upon submission the form data, and any associated files, are stored in an encrypted file within the Managed Forms Vault
- a record of the submission is stored in the messaging table (dcmThread) along with minimal identifying information*
- a notification may be automatically sent to the website owner
- a confirmation may, optionally, be sent to the person making the submission**
*When building the managed form the developer selects which identifying information to leave unencrypted - typically just the name of the submitter.
**If an email is sent to the submitter it should be done using a From address containing the website's domain. This requires additional configuration not given on this page, see details below.
Although Managed Forms are considerably easier to code than Enhanced Forms, the list above illuminates that this is not necessarily a simple process.
Basic Example
Let's start with a simple example which covers the primary needs of most website forms.
The Form
The first step is to plan the form's UI. A simple example follows:
<p>
We'd love to host your next event!
</p>
<dcf.ManagedForm TitleFields="Name,Date" Name="Event">
<dcf.Text Label="Name" Name="Name" />
<dcf.Text Label="Date" placeholder="" Name="Date" />
<dcf.Text Label="Time" placeholder="" Name="Time" />
<dcf.Text Label="Email" Name="Email" />
<dcf.Text Label="Phone" Name="Phone" />
<dcf.Text Label="Number of Attendees" placeholder="" Name="Attendees" />
<dcf.TextArea Label="Event" placeholder="describe your event" Name="Event" />
<dcf.FormButtons>
<dcf.SubmitButton Label="Submit" />
</dcf.FormButtons>
</dcf.ManagedForm>
This form presents six simple single line text input fields and one multiple line text input field, along with a Submit button. There are many additional input types, review the list of Enhanced Form Tags for some details, and we'll cover a few more examples later in this page. The key pieces to notice at first are:
-
each input field needs a
Label
and aName
, the latter is the internal name of the field and should not contain spaces or special characters. These two attributes are enhanced - not part of HTML. -
Regular attributes such as
placeholder
,class
andid
keep their normal casing and may be used -
the
dcf.FormButtons
tag right aligns and adds padding around any buttons included - no additional code is required for a SubmitButton, it just works (no additional script or POST location needed)
We'll cover a few more complex examples later in this document.
Form Configuration
In order for a form submission to work at all it needs to be configured in the website's
config.xml
file. The naming convention for the configuration
Id
is
CMS-ManagedForm-
then the form name (in this case
Event
) and finally followed by the scope. The scope will almost always be
-Both
. For additional details on how
Catalog
works see here TODO.
<Catalog Id="CMS-ManagedForm-Event-Both">
<Settings Type="harEventForm" Script="/har/event-event-form-submitted.dcs.xml" />
</Catalog>
Since our form is named
Event
that links it to this Catalog entry and the Settings within. There are only two settings:
- Type: the name of the data type for validating the form
- Script: the script to run for notifications
Data Validation
First let's review the data type. Data types are defined in the
schema.xml
file, so go to the same folder as the
config.xml
file is in (unless this is a subsite - in which case go to the root site - TODO see schema-belongs-in-root). If there is no schema file then create it and use the following as a template:
<Schema>
<Shared>
<Record Id="harEventForm">
<Field Name="Name" Type="dcSmallString" Required="true" />
<Field Name="Date" Type="dcSmallString" Required="true" />
<Field Name="Time" Type="dcSmallString" Required="true" />
<Field Name="Email" Type="dcEmail" Required="true" />
<Field Name="Phone" Type="dcSmallString" Required="true" />
<Field Name="Attendees" Type="dcSmallString" />
<Field Name="Event" Type="String" Required="true" />
</Record>
</Shared>
</Schema>
What we see here is that
harEventForm
is a Shared (website wide) datatype and that it is a Record. Records consist of fields, and each field is listed here by
Name
. Note that the field name here must match the name of the input field from the form. Each Field has two key features - is it
Required
or not (simply leave off if not, such as with Attendees), and
Type
which is the data type. For these examples we'll stick to a few string data types.
- String: an (effectively) unlimited free text field
- dcSmallString: also a free text text field but limited to no more than 250 characters
- dcString: also a free text text field but limited to no more than 4000 characters (this may have been an appropriate option for the Event field)
- dcEmail: this must match a specific pattern of characters
- Integer: limits the field to whole numbers (this may have been appropriate for Attendees)
- Boolean: true or false fields (the YesNo or Checkbox inputs)
There are many other field (scalar) data types, and it is also easy to build your own, but these data types cover almost all the typical fields used with Managed Forms.
Note that you must list each field from the form and use the same Name. If the form contains names not in the schema then it will fail to submit. On the other hand, the form does not need to list all the fields from the schema - though it must list all the required fields. It is recommended that you do not remove fields from the schema if a field is no longer needed, instead simply make the field optional in the schema and then remove it from the form. The reason for this is that any data previously submitted (and stored in those encrypted files) will still have that old field. Keeping the field in the schema will help preserve the ability to validate the old data and that may come in handy with future enhancements to display previously submitted data via the CMS.
Submitted Data Access
While we have the data structures needed, there is currently no standard UI for presenting the submitted data via the CMS. Adding this ability would not be much effort and it would certainly make sense with more sensitive information. When submitting sensitive information, ideally the notice (see below) would contain minimal identifying information and then the website owner would then login and review the details through the secure website. However, currently, there is no standardized approach to this - either website owner receives all the information (and files) via the notice or they use a custom UI to view the submission details.
TODO expand - how to see message details
Notifications
An script is executed after the form has been securely stored in the vault (encrypted file). The script provides an opportunity for any or all of the following:
- sending an appropriately formatted notice to the website owner (or a representative thereof)
- sending a notice to the submitter
- running some automated data processing on the data submitted
Recall from above that we have this in our config file:
<Catalog Id="CMS-ManagedForm-Event-Both">
<Settings Type="harEventForm" Script="/har/event-event-form-submitted.dcs.xml" />
</Catalog>
The path for the
Script
assumes the folder
script
- so the full path of the script within the website folder is
/script/har/event-event-form-submitted.dcs.xml
. We'll review the script soon, but first note that in the config file we also defined our mailing list destinations. The naming convention for the mailing list
Id
is
Email-List-ManagedForm-
then the list name (in this case
Event
to match the form name - but it need not match the form name) and finally followed by the scope. The scope will almost always be split to
-Test
and
-Production
so that test emails can be generated during website development. For additional details on how
Catalog
works see here TODO.
<Catalog Id="Email-List-ManagedForm-Event-Production">
<Settings To="nardiharmony@gmail.com;andy@designcraftadvertising.com" />
</Catalog>
<Catalog Id="Email-List-ManagedForm-Event-Test">
<Settings To="andy@designcraftadvertising.com" />
</Catalog>
Given the above, we see that when in development emails will go to just andy, whereas in production emails go to andy and nardi (note the semicolon delimiter is required for each additional destination). The email list is a handy place to list the addresses, but it is the script that builds and sends the email, so let's review that now.
<dcs.Script Title="Managed Event Form Submitted">
<dcs.Info>Just got an event form submission: {$_Param.Id}!</dcs.Info>
<dcdb.LoadRecord Table="dcmThread" Id="{$_Param.Id}" Result="Thread">
<Select Field="dcmTitle" As="Title" />
<Select Field="dcmManagedFormName" As="Form" />
<Select Field="dcmUuid" As="Uuid" />
<Select Field="dcmManagedFormToken" As="Token" />
</dcdb.LoadRecord>
<dcs.File Name="datafile" Path="/{$_Param.Id}/data.json" In="ManagedForms" />
<dcs.With Target="$datafile">
<ReadText Result="datastr" />
</dcs.With>
<dcs.Var Name="Form" Type="Record">
<Set>{$datastr}</Set>
</dcs.Var>
<text Name="textEmail"><![CDATA[## Event Request Form
**Name:** {$Form.Data.Name}
**Date:** {$Form.Data.Date}
**Time:** {$Form.Data.Time}
**Email:** {$Form.Data.Email}
**Phone:** {$Form.Data.Phone}
**Attendees:** {$Form.Data.Attendees}
**Event Info:**
{$Form.Data.Event}
]]></text>
<dcs.SendEmail ToList="ManagedForm-Event" Subject="Harmony Bar {$Thread.Title}" TextMessage="$textEmail" ReplyTo="{$Form.Data.Email}" />
</dcs.Script>
For the most part when creating a script it is reasonable to just copy this script, or one like it. In that situation the code you are most interested in is the
<text>
tag and the
<dcs.SendEmail>
tag. As the script runs it loads the data file from the vault, which also decrypts it, and then it parses the data file into JSON. By the time we get to the
<text>
instruction there is the
$Form.Data
variable which holds all the data fields from the form.
In the
<text>
instruction simply build a markdown document, containing all necessary form data, within the CDATA section. This markdown is then converted to HTML when the email is sent. However, since all emails should contain both a text and a HTML option - the markdown is sent as the text option (even though almost no one will ever see it). You should always build a text option (as we do above), but if you want it to be formatted differently from the HTML there is a way to specify the HTML option separately. TODO link to building emails in scripts
The
<dcs.SendEmail>
command specifies (using the
ToList
attribute) which email list to use (it adds
Email-List-
to the start and the scope to the end), so in our example
ManagedForm-Event
matches the mailing lists discussed above. The
TextMessage
attribute indicates the source of the message content. The
Subject
attribute indicates the subject line of the email. And, finally, the
ReplyTo
attribute indicates the recipient if one clicks reply. Since this example notice is going to the website owner (Nardi) the reply value makes it easy for her to reply to requests submitted online through her email software.
TODO - note that notices may also be texts to a phone instead of emails
Notifications To Submitter
If the website also wishes to send a confirmation email to the submitter, just add these lines to the script above:
<text Name="textEmailConfirm"><![CDATA[
Dear {$Form.Data.Name},
Thank you for contacting us about your event on {$Form.Data.Date} at {$Form.Data.Time} - we will review the request and respond within two business days.
Your neighbors,
Harmony Bar and Grill
]]></text>
<dcs.SendEmail To="{$Form.Data.Email}" Subject="Harmony Bar Event Received" TextMessage="$textEmailConfirm" />
The copy for the confirmation could say anything, including listing all the variables again, however it may include only a summary as this example does.
Instead of using
ToList
we use
To
and point it to the submitter's email. The
ReplyTo
has been removed, for although it could be hard coded to the webmaster's email, we'll rely instead on the default From email which is part of the website email setup (TODO link to article on email setup).
Even though there isn't much additional code needed to send a confirmation, I recommend not sending a this confirmation. For one, the user is told that the submission was successful via the website - they don't need another confirmation. But, furthermore, if notices are only being sent to the website owner and their staff then it is okay for the email to come from the system's default webmaster (the agency that coded the website). But if the email is going to customers then the From field should be from the website owner. In order to support that the domain name must first be verified in AWS, see email setup link above. The verification and activation process is time consuming and therefore should only be done if confirmation emails are important.
Example with Complex Data Types
Form
Expanding on the previous example we'll add in a YesNo field, a RadioGroup, a CheckGroup and a Checkbox:
<dcf.ManagedForm TitleFields="Name,Date" Name="Event">
<dcf.Text Label="Name" Name="Name" />
<dcf.Text Label="Date" placeholder="" Name="Date" />
<dcf.Text Label="Time" placeholder="" Name="Time" />
<dcf.Text Label="Email" Name="Email" />
<dcf.Text Label="Phone" Name="Phone" />
<dcf.Text Label="Number of Attendees" placeholder="" Name="Attendees" />
<dcf.YesNo Label="Open Bar" Name="OpenBar" />
<dcf.HorizRadioGroup Label="Type of Event" Name="EventType" Instructions="We provide special pricing on the pool table for birthdays and on the stage for anniversaries.">
<RadioButton Value="Birthday" Label="Birthday" />
<RadioButton Value="Anniversary" Label="Anniversary" />
<RadioButton Value="Other" Label="Other" />
</dcf.HorizRadioGroup>
<dcf.CheckGroup Label="Pizza Types" Name="PizzaTypes" Instructions="We make enough cheese pizza to feed 1/2 of your attendees, the other 1/2 are split equally across your selections below:">
<Checkbox Label="Pepperoni" Value="Pepperoni" />
<Checkbox Label="Sausage" Value="Sausage" />
<Checkbox Label="Veggie" Value="Veggie" />
<Checkbox Label="Gluten Sensitive" Value="GlutenSensitive" />
</dcf.CheckGroup>
<dcf.TextArea Label="Event" placeholder="describe your event" Name="Event" />
<dcf.Checkbox Name="ReadGuide" LongLabel="I have read the events guideline PDF." />
<dcf.FormButtons>
<dcf.SubmitButton Label="Submit" />
</dcf.FormButtons>
</dcf.ManagedForm>
The CheckGroup and RadioGroup both have instructions added to them. This is a feature available to all field types. Note that the RadioGroup could also have been a Select element. Many form building options are available under the Enhanced Forms section.
Data type changes:
- the YesNo field (OpenBar) and the Checkbox (ReadGuide) will be considered a Boolean (true/false) type
- the RadioGroup (EventType) and the CheckGroup (PizzaTypes) will both be specialized enumerated types
First let's make the data types for the enumerated fields. These would both be added to the
<Shared>
section of the schema.
<StringType Id="harEventTypeEnum">
<StringRestriction Enum="Birthday,Anniversary,Other" />
</StringType>
<StringType Id="harPizzaTypeEnum">
<StringRestriction Enum="Pepperoni,Sausage,Veggie,GlutenSensitive" />
</StringType>
Now add the fields to the event form data type:
<Record Id="harEventForm">
<Field Name="Name" Type="dcSmallString" Required="true" />
<Field Name="Date" Type="dcSmallString" Required="true" />
<Field Name="Time" Type="dcSmallString" Required="true" />
<Field Name="Email" Type="dcEmail" Required="true" />
<Field Name="Phone" Type="dcSmallString" Required="true" />
<Field Name="Attendees" Type="Integer" />
<Field Name="OpenBar" Type="Boolean" />
<Field Name="EventType" Type="harEventTypeEnum" />
<Field Name="PizzaTypes">
<List Type="harPizzaTypeEnum" />
</Field>
<Field Name="Event" Type="String" Required="true" />
<Field Name="ReadGuide" Type="Boolean" Required="true" />
</Record>
Notice:
-
the two enumerated types use the special data type (in
Type
) - switched Attendees to Integer so the user must enter a number
- the order in which fields appear in the data type doesn't really matter, though listing in the same order as on the form helps with readability
-
the pizza field can have more than one response, check all that apply, so note how that field contains a
List
of a special type unlike the other fields which have only one type.
Now in the email notice add these new fields:
<text Name="textEmail"><![CDATA[## Event Request Form
**Name:** {$Form.Data.Name}
**Date:** {$Form.Data.Date}
**Time:** {$Form.Data.Time}
**Email:** {$Form.Data.Email}
**Phone:** {$Form.Data.Phone}
**Attendees:** {$Form.Data.Attendees}
**Open Bar:** {$Form.Data.OpenBar|yn:}
**Event Type:** {$Form.Data.EventType}
**Pizza Types:** {$Form.Data.PizzaTypes|join:, |ifempty:[none selected]}
**Guidelines Read:** {$Form.Data.ReadGuide|yn:}
**Event Info:**
{$Form.Data.Event}
]]></text>
Notice:
-
both of the Boolean data types have been formatted above using
|yn:
which converts true/false to yes/no for human readability. - event type is unformatted and so will show it's value only - through enumeration may have translations if dictionaries are used (TODO link to translations)
-
pizza types is a list so it can be joined into a comma list using the
|join:
formatter - in this case adding a comma and space between each. Furthermore, if the list is empty then the|ifempty:
formatter adds “[none selected]”
Along with more advanced field types, it is relatively easy to expand on the features of Managed or Enhanced Forms.
Example of Forms with Files
Managed Forms have the built-in option to support a single file upload prompt. It does allow for multiple files, but only one prompt or field. Extending on the topics above, the additions would be:
<dcf.ManagedForm Name="Estimate" TitleFields="Name">
<dcf.Text Label="Name" Name="Name" />
<dcf.Text Label="Email" Name="Email" />
<dcf.Text Label="Phone" Name="Phone" />
<dcf.TextArea Label="Description" Name="Description" placeholder="Description of your project" />
<dcf.Uploader Label="Estimate Files" Name="Attachments" data-max-size="7340032" Instructions="Please limit total file size to 7MB." />
<dcf.FormButtons>
<dcf.SubmitButton Label="Submit" />
</dcf.FormButtons>
</dcf.ManagedForm>
The addition of
dcf.Uploader
and the
Name
of “Attachments”. The
Label
and
Instructions
can be whatever you like, and as always the
Instructions
are optional. A managed form with an Uploader named “Attachments” is the key to the automated process of a managed form.
No special addition is needed in the config.xml for a form with Attachments. For example, the above form would be exactly the same with or without the Attachments:
<Catalog Id="CMS-ManagedForm-Estimate-Both">
<Settings Type="ttrEstimateForm" Script="/ttr/event-estimate-form-submitted.dcs.xml" />
</Catalog>
The list of file names will be sent to the server along with the other information from the form (Name, Email, Phone, Description above). So to accommodate those names you'll need to add Attachments to your schema as well. Set it to be a list of strings. For example:
<Schema>
<Shared>
<Record Id="ttrEstimateForm">
<Field Name="Name" Type="dcSmallString" Required="true" />
<Field Name="Email" Type="dcEmail" Required="true" />
<Field Name="Phone" Type="dcMetaString" Required="true" />
<Field Name="Description" Type="String" Required="true" />
<Field Name="Attachments">
<List Type="dcMetaString" />
</Field>
</Record>
</Shared>
</Schema>
The addition of the Attachments to the email notification is easy to code but can be unreliable. Many email services limit attachment sizes - sometimes the total size of all the attachments is limited and sometimes the size of individual attachments is limited. If you noticed the form above has
data-max-size
, the primary reason for using that with that website is to make sure the attachments can be emailed. In general, as noted before, it is preferable not to email details or files in the notification. Rather it would be best for the notification to simply indicate new action is required and for the client to sign into the (secure) website to view the content and the files.
However, most websites will prefer the attachments to be in the email. To support that simply add this code before the
dcs.SendEmail
tag.
<dcs.Var Name="Attachments" Type="List" />
<dcs.If Target="$Form.Data.Attachments" IsEmpty="false">
<dcs.ForEach Name="Attachment" In="$Form.Data.Attachments">
<dcs.File Name="addfile" Path="/{$_Param.Id}/{$Attachment}" In="ManagedForms" />
<dcs.Var Name="AddRec" Type="Record">
<SetField Name="Name" Value="$Attachment" />
<SetField Name="File" Value="$addfile" />
</dcs.Var>
<dcs.With Target="$Attachments">
<AddItem Value="$AddRec" />
</dcs.With>
</dcs.ForEach>
</dcs.If>
While it may look complex, there is no need to understand or edit the code - just use it as is. There is a further addition required, the addition of the
Attachments
attribute to SendEmail. Example:
<dcs.SendEmail ToList="ManagedForm-Estimate" Subject="{$Thread.Title}" TextMessage="$textEmail" ReplyTo="{$Form.Data.Email}" Attachments="$Attachments" />
Note that
$Attachments
is the variable defined in the code block above.
With those two easy adjustments your email notice is now sending the uploaded files as attachments to the email.
Example with CAPTCHA Services
I no longer recommend using reCAPTCHA on the dcServer forms. We find that very few forms are spammed, since we do not use the POST back feature of forms. And clients are always concerned about interfering with, or confusing, clients. But following is an overview if you have to support it.
Quick summary of set up (TODO move to own page and link to that).
Technically we can support any of the following options:
- reCAPTCHA v2 (“I'm not a robot” Checkbox)
- reCAPTCHA v2 (Invisible reCAPTCHA badge)
- reCAPTCHA v3
However, most of the time it is fine to use the reCAPTCHA v2 (Invisible reCAPTCHA badge) option, and it is the easiest to set up. reCAPTCHA v3 is simply to unreliable for form verification.
First go and get a new site key. Learn about how to do so here:
https://developers.google.com/recaptcha/docs/versions
Make sure you list the website domain as well as the developer domain, subdomains don't matter. For example:
tonytrappllc.com
designcraftadvertising.com
Would along either domain to be used when testing the form.
Once you have the Site Key and Secret Key edit the config.xml for your website:
<Catalog Id="Google-Both">
<Settings TrackingCode="UA-2478599-00">
<reCAPTCHA SiteKey="6Ldk9ocUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" SecretKey="6Ldk9ocUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" />
</Settings>
</Catalog>
Place the
reCAPTCHA
tag inside the
Settings
tag you normally use for the website Google Analytics code.
After this, the only remaining step is to replace the submit button:
<dcf.SubmitButton Label="Submit" />
becomes:
<dcf.SubmitCaptchaButton Label="Submit" Action="submit_contact" />
Provide an appropriate
Action
label (attribute) such as “contact” or “contest” or “pledge”, no spaces or special characters besides dash or underscore.
With the submit button changes and the key in place, the form should now automatically use reCAPTCHA to verify the human user. With the Invisible option the prompt will not always happen, but behind the scenes Google is checking for a real user.
hCAPTCHA Alternative
hCAPTCHA provides an alternative to Google's reCAPTCHA v2 services. The only difference needed on the website is to the config.xml file, use a “CAPTCHA” catalog instead of the “Google” catalog, like so:
<Catalog Id="CAPTCHA-Both">
<Settings Service="hCAPTCHA">
<hCAPTCHA SiteKey="00000000-0000-0000-0000-000000000"
SecretKey="0x00000000000000000000000000000000000000" />
</Settings>
</Catalog>
To get your key and secret, you'll need to sign up for the service:
https://www.hcaptcha.com/